I am implementing Google Cast for my video app (min API 16) with live HLS streams. I follow the instructions https://codelabs.developers.google.com/codelabs/cast-videos-android/#5 and stopped in step 6. When I tap on cast button, Cast dialog appears, I select my Android TV device (test device Mibox 3, Android 6), remote player is trying to load video, but instead of HLS live video I see a big red title SAMPLE on TV. I decided to play sample video http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8 and again I see SAMPLE. If I tap on cast button again, dialog indicates: no media file was chosen. Locally I use ExoPlayer to play HLS live video without problems. I tried MediaInfo.STREAM_TYPE_BUFFERED and MediaInfo.STREAM_TYPE_LIVE
Code:
private void setupCastListener() {
mSessionManagerListener = new SessionManagerListener<CastSession>() {
#Override
public void onSessionEnded(CastSession session, int error) {
onApplicationDisconnected();
}
#Override
public void onSessionResumed(CastSession session, boolean wasSuspended) {
onApplicationConnected(session);
}
#Override
public void onSessionResumeFailed(CastSession session, int error) {
onApplicationDisconnected();
}
#Override
public void onSessionStarted(CastSession session, String sessionId) {
onApplicationConnected(session);
}
#Override
public void onSessionStartFailed(CastSession session, int error) {
onApplicationDisconnected();
}
#Override
public void onSessionStarting(CastSession session) {}
#Override
public void onSessionEnding(CastSession session) {}
#Override
public void onSessionResuming(CastSession session, String sessionId) {}
#Override
public void onSessionSuspended(CastSession session, int reason) {}
private void onApplicationConnected(CastSession castSession) {
mCastSession = castSession;
if (clickedChannel != null) {
if (isPlaying()) {
loadRemoteMedia(true);
}
}
}
private void onApplicationDisconnected() {
}
};
}
private void loadRemoteMedia(boolean autoPlay) {
if (mCastSession == null) {
return;
}
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
if (remoteMediaClient == null) {
return;
}
remoteMediaClient.load(buildMediaInfo(), autoPlay);
}
private MediaInfo buildMediaInfo() {
MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
mediaMetadata.putString(MediaMetadata.KEY_TITLE, clickedChannel.getName());
selectedStreamingURL = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8";
return new MediaInfo.Builder(selectedStreamingURL)
.setContentType("application/x-mpegURL")
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setMetadata(mediaMetadata)
.build();
}
Problem was solved by changing this method
#Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.build();
}
Related
I am trying to create a Xamarin Forms application that displays a full screen video for the launch page. Once loading is complete the launch page takes you to the home screen. The home screen displays a series of videos in a carousel.
On Android the carousel videos work fine by themselves but when I add in the launch screen video it displays over the carousel ones. Currently I have a View called VideoView in my App that has a BindableProperty for the video source.
The rendering is done platform specifically in a custom renderer that is below. It uses a native VideoView and MediaPlayer control for Android. I have tried the various dispose and and release functions in the OnDisappearing function in the launch screen, but none of them seem to release the video.
[assembly: ExportRenderer(typeof(App.Renderers.VideoView),
typeof(VideoViewRenderer))]
namespace App.Droid.Renderers
{
public class VideoViewRenderer :
ViewRenderer<App.Renderers.VideoView, VideoView>, ISurfaceHolderCallback
{
private VideoView videoview;
private MediaPlayer player;
public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Format format, int width, int height)
{
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<App.Renderers.VideoView> e)
{
base.OnElementChanged(e);
e.NewElement.StopAction = () =>
{
player.Pause();
};
e.NewElement.StartAction = () =>
{
player.Start();
};
e.NewElement.DisposeAction = () =>
{
Control.StopPlayback();
Control.Dispose();
player.Stop();
player.Reset();
player.Release();
};
if (Control == null)
{
videoview = new VideoView(Context);
base.SetNativeControl(videoview);
Control.Holder.AddCallback(this);
player = new MediaPlayer();
play(e.NewElement.FileSource);
}
}
void play(string fullPath)
{
AssetFileDescriptor afd = Forms.Context.Assets.OpenFd(fullPath);
if (afd != null)
{
try
{
player.SetDataSource(afd.FileDescriptor, afd.StartOffset, afd.Length);
player.Prepare();
player.Looping = true;
Control.Layout(0, 200, player.VideoHeight, player.VideoWidth);
player.SetVideoScalingMode(VideoScalingMode.ScaleToFit);
}
catch
{
}
}
}
public void SurfaceCreated(ISurfaceHolder holder)
{
player.SetDisplay(holder);
}
}
}
The VideoView class extends View and is as follows:
public class VideoView : View
{
public Action StopAction;
public Action StartAction;
public Action DisposeAction;
public VideoView()
{
FileSource = string.Empty;
}
public static readonly BindableProperty FileSourceProperty =
BindableProperty.Create<VideoView, string>(
p => p.FileSource, string.Empty);
public string FileSource
{
get { return (string)GetValue(FileSourceProperty); }
set { SetValue(FileSourceProperty, value); }
}
public void Stop()
{
if (StopAction != null)
StopAction();
}
public void Dispose()
{
if (DisposeAction != null)
DisposeAction();
}
public void Start()
{
if (StartAction != null)
StartAction();
}
}
And finally usage of the view is as follows:
<controls:VideoView Grid.Row="0" Grid.Column="0" x:Name="Video1" FileSource="big_buck_bunny.mp4" Margin="0,10,0,40" />
I want to start (on background) sync with Realm Object Server when launching my Android app. And after data was successfully downloaded I want to show toast.
How I can do this? What method do I need to use?
Thanks.
I have not tried this but it should work.
private void setRealmDefaultConfiguration(SyncUser syncUser, String realmURL) {
SyncConfiguration config = new SyncConfiguration.Builder(syncUser, realmURL)
.waitForInitialRemoteData()
.build();
Realm.setDefaultConfiguration(config);
}
public abstract class BaseActivity extends Activity {
private static boolean firstInit = true;
protected Realm realm = null;
#Override
public void onCreate(Bundle bundle) {
final boolean shouldShowToast;
if(firstInit) {
firstInit = false;
shouldShowToast = true;
} else {
shouldShowToast = false;
}
super.onCreate(bundle);
Realm.getInstanceAsync(Realm.getDefaultConfiguration(), new Realm.Callback() {
#Override
public void onSuccess(Realm realm) {
if(isChangingConfigurations() || isFinishing()) {
realm.close();
} else {
BaseActivity.this.realm = realm;
onRealmLoaded(realm);
}
if(shouldShowToast) {
Toast.makeText(BaseActivity.this, R.string.data.loaded, Toast.LENGTH_LONG).show();
}
}
#Override
public void onError(Throwable throwable) {
// boop
}
});
}
protected void onRealmLoaded(Realm realm) {
// override this if needed
}
}
I am implementing the android native fingerprint API for one of our applications.
Below is the code of the handler:
public class FingerprintHandler extends
FingerprintManager.AuthenticationCallback {
private CancellationSignal cancellationSignal;
private Context appContext;
private boolean sendCancellation=false;
public FingerprintHandler(Context context) {
appContext = context;
}
public void startAuth(FingerprintManager manager,
FingerprintManager.CryptoObject cryptoObject) {
cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(appContext,
Manifest.permission.USE_FINGERPRINT) !=
PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}
#Override
public void onAuthenticationError(int errMsgId,
CharSequence errString) {
Log.d(TAG,"Authentication error callback");
Log.d(TAG,"Error Value: "+errMsgId);
switch(errMsgId){
case FINGERPRINT_ERROR_LOCKOUT:
Log.d(TAG,"Fingerprint error lockout");
nativeLocked = true;
mPreferences.edit().putLong(LAST_FAILURE, System.currentTimeMillis()).apply();
showError(getString(R.string.test_bio_fingerprint_fingerprint_authentication_failed));
mCancelButton.setEnabled(true);
dialogHandler.postDelayed(new Runnable() {
#Override
public void run() {
mCancelButton.setEnabled(true);
dismissDialog();
sendFailure();
}
}, SUCCESS_OR_FAIL_DELAY_MILLIS);
break;
case FINGERPRINT_ERROR_CANCELED:
Log.d(TAG,"Fingerprint has been cancelled");
if(sendCancellation) {
dismissDialog();
if (useEye)
sendCancelForEye();
else
sendCancel();
}
break;
}
}
#Override
public void onAuthenticationHelp(int helpMsgId,
CharSequence helpString) {
Log.d(TAG,"Authentication helper callback");
retryWithError(R.string.test_bio_fingerprint_fingerprint_too_fast);
}
#Override
public void onAuthenticationFailed() {
Log.d(TAG,"Authentication failed callback");
retryWithError(R.string.test_bio_fingerprint_fingerprint_not_recognized);
}
#Override
public void onAuthenticationSucceeded(
FingerprintManager.AuthenticationResult result) {
Log.d(TAG,"Authentication successfull callback");
onAuthenticationSuccess();
}
public void stopListening(boolean sendCancellation) {
this.sendCancellation=sendCancellation;
Log.d(TAG,"stopListening called");
if (cancellationSignal != null) {
cancellationSignal.cancel();
cancellationSignal = null;
}
}
}
The problem is that when there are 5 incorrect authentication attempts I can see the below in my logs:
Authentication error callback
Error Value: 7
Fingerprint error lockout
Authentication error callback
Error Value: 7
Fingerprint error lockout
The lockout call back gets twice. I am unable to figure out why this is happening. Can someone please help.
You need to check errMsgId because first is triggered for your action callback and second time is triggered for "Fingerprint operation canceled."
Also please notice that FingerPrintManager is deprecated and for the new api for BiometricPrompt the implementation should look like example below (you need to replace BiometricConstants.ERROR_NEGATIVE_BUTTON with your action id code):
#Override
public void onAuthenticationError(int errorCode,
#NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if (BiometricConstants.ERROR_NEGATIVE_BUTTON == errorCode) {
//do your job only once
}
}
I am implementing an android fingerprint authentication. I want to know which user, who has registered in device before, is authenticating. Is there any information about the user, who has registered and is valid for the device, in the FingerprintManager.AuthenticationResult argument in onAuthenticationSucceeded method?!
I am using this sample.
this is my class, which is implementing FingerprintManager.AuthenticationCallback:
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
private static final long ERROR_TIMEOUT_MILLIS = 1600;
private static final long SUCCESS_DELAY_MILLIS = 1300;
private final FingerprintManager mFingerprintManager;
private final ImageView mIcon;
private final TextView mErrorTextView;
private final Callback mCallback;
private CancellationSignal mCancellationSignal;
private boolean mSelfCancelled;
/**
* Constructor for {#link FingerprintUiHelper}.
*/
FingerprintUiHelper(FingerprintManager fingerprintManager,
ImageView icon, TextView errorTextView, Callback callback) {
mFingerprintManager = fingerprintManager;
mIcon = icon;
mErrorTextView = errorTextView;
mCallback = callback;
}
public boolean isFingerprintAuthAvailable() {
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable()) {
return;
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
mFingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
mIcon.setImageResource(R.drawable.ic_fp_40px);
}
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
#Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (!mSelfCancelled) {
showError(errString);
mIcon.postDelayed(new Runnable() {
#Override
public void run() {
mCallback.onError();
}
}, ERROR_TIMEOUT_MILLIS);
}
}
#Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
showError(helpString);
}
#Override
public void onAuthenticationFailed() {
showError(mIcon.getResources().getString(
R.string.fingerprint_not_recognized));
}
#Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mIcon.setImageResource(R.drawable.ic_fingerprint_success);
mErrorTextView.setTextColor(
mErrorTextView.getResources().getColor(R.color.success_color, null));
mErrorTextView.setText(
mErrorTextView.getResources().getString(R.string.fingerprint_success));
mIcon.postDelayed(new Runnable() {
#Override
public void run() {
mCallback.onAuthenticated();
}
}, SUCCESS_DELAY_MILLIS);
}
private void showError(CharSequence error) {
mIcon.setImageResource(R.drawable.ic_fingerprint_error);
mErrorTextView.setText(error);
mErrorTextView.setTextColor(
mErrorTextView.getResources().getColor(R.color.warning_color, null));
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
}
private Runnable mResetErrorTextRunnable = new Runnable() {
#Override
public void run() {
mErrorTextView.setTextColor(
mErrorTextView.getResources().getColor(R.color.hint_color, null));
mErrorTextView.setText(
mErrorTextView.getResources().getString(R.string.fingerprint_hint));
mIcon.setImageResource(R.drawable.ic_fp_40px);
}
};
public interface Callback {
void onAuthenticated();
void onError();
}
}
i'm new in Rx programming (and I'm having a lot of fun so far ^^).
I'm trying to transform a AsyncTask call into an Rx function.
My function :
Get all the installed apps
normalize the labels
sort everything alphabetically
arrange them by group of letter (it was a Multimap(letter, list of apps)) and pass the result to an adapter to display everything.
Here is how I'm doing so far with Rx :
Observable.from(getInstalledApps(getActivity(), false))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<ResolvedActivityInfoWrapper, ResolvedActivityInfoWrapper>() {
#Override
public ResolvedActivityInfoWrapper call(ResolvedActivityInfoWrapper act) {
// Normalize labels
act.setLabel(Normalizer.normalize(act.getLabel(getPackageManager()).replace(String.valueOf((char) 160), "").trim(), Normalizer.Form.NFD).replaceAll("\\p{M}", ""));
return act;
}
})
.toList()
.subscribe(new Observer<List<ResolvedActivityInfoWrapper>>() {
List<ResolvedActivityInfoWrapper> list;
#Override
public void onCompleted() {
Observable.from(list).groupBy(new Func1<ResolvedActivityInfoWrapper, String>() {
#Override
public String call(ResolvedActivityInfoWrapper input) {
//Get groups by letter
String label = input.getLabel(getPackageManager());
if (!TextUtils.isEmpty(label)) {
String firstChar = label.substring(0, 1);
if (pattern.matcher(firstChar).matches()) {
return firstChar.toUpperCase();
}
}
return "#";
}
}).subscribe(this); // implementation below
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<ResolvedActivityInfoWrapper> list) {
Collections.sort(list, new Comparator<ActivityInfoWrapper>() {
#Override
// Sort all the apps in the list, not sure it's a good way to do it
public int compare(ActivityInfoWrapper info1, ActivityInfoWrapper info2) {
return info1.getLabel(getPackageManager()).compareToIgnoreCase(info2.getLabel(getPackageManager()));
}
});
this.list = list;
}
});
Once I groupedBy letters, on complete I subscribe with this :
#Override
public void onCompleted() {
//display the apps
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(GroupedObservable<String, ResolvedActivityInfoWrapper> input) {
//For each list of apps by letter i subscribe with an observer that will handle those apps (observer code below)
input.subscribe(new TestObserver(input.getKey()));
}
Observer :
private class TestObserver implements Observer<ResolvedActivityInfoWrapper> {
List<ResolvedActivityInfoWrapper> list;
String letter;
public TestObserver(String letter) {
list = new ArrayList<>();
this.letter = letter;
}
#Override
public void onCompleted() {
adapter.addData(letter, list);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(ResolvedActivityInfoWrapper input) {
list.add(input);
}
}
Everything works correctly excpets for one problem : the observer's onCompleted are called not in the right order. So I got all my apps, sorted by letter, but the groups are nots displayed in the right order (C first, then Y, then M etc ...).
I guess there are plenty of errors in the code, can you help me with this probleme and maybe understanding how all this works please ?
Thanks
UPDATE :
Following the advices in the commentary section (thanks people), here is what I'm trying after normalizing the labels :
Observable.from(list).groupBy(new Func1<ResolvedActivityInfoWrapper, String>() {
#Override
public String call(ResolvedActivityInfoWrapper input) {
String label = input.getLabel(getPackageManager());
if (!TextUtils.isEmpty(label)) {
String firstChar = label.substring(0, 1);
if (pattern.matcher(firstChar).matches()) {
return firstChar.toUpperCase();
}
}
return "#";
}
})
.toSortedList(new Func2<GroupedObservable<String, ResolvedActivityInfoWrapper>, GroupedObservable<String, ResolvedActivityInfoWrapper>, Integer>() {
#Override
public Integer call(GroupedObservable<String, ResolvedActivityInfoWrapper> obs1, GroupedObservable<String, ResolvedActivityInfoWrapper> obs2) {
return obs1.getKey().compareToIgnoreCase(obs2.getKey());
}
})
.subscribe(new Observer<List<GroupedObservable<String, ResolvedActivityInfoWrapper>>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<GroupedObservable<String, ResolvedActivityInfoWrapper>> input) {
String test = input.get(0).getKey();
}
});
But it never goes into the Compare function.