Customizing playback controls in a Leanback app - android

I have a pretty basic task: implementing HLS video streaming using Leanback framework and Exoplayer.
I use a simple single-fragment layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/video_fragment"
android:name="android.support.v17.leanback.app.VideoFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Apparently, VideoFragment comes with a default HUD which looks like this:
According to the docs, the recommended approach to bridging media player with playback controls is via PlaybackGlue. For example, Leanback showcase app uses MediaPlayer and thus uses a MediaPlayerGlue. I, for my purposes, have taken ExoPlayerGlue from this project.
private PlaybackFragmentGlueHost glueHost;
private PlaybackControlGlue glue;
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
final VideoFragment videoFragment = (VideoFragment) getFragmentManager.findFragmentById(R.id.video_fragment);
glueHost = new VideoFragmentGlueHost(videoFragment);
glue = ExoPlayerGlue(this, player)
glue.setHost(glueHost);
}
This code does the job: it binds clicks to player events, so pause/resume and rewinding/fast-forwarding work as expected.
The tricky part is customizing playback controls. PlaybackGlueHost provides setPlaybackRow(Row row) and setPlaybackRowPresenter(PlaybackRowPresenter presenter) methods. Worth pointing out is that these methods have no effect in onCreate() so I either have to use handler or move them to onPostCreate():
#Override
public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Buttons I need in my HUD
final Action skipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(this); // |<
final Action rewindAction = PlaybackControlsRow.RewindAction(this); // <<
final Action playPauseAction = PlaybackControlsRow.PlayPauseAction(this);
final Action fastForwardAction = PlaybackControlsRow.FastForwardAction(this); // >>
final Action skipNextAction = PlaybackControlsRow.SkipNextAction(this); // >|
final Action repeatAction = PlaybackControlsRow.RepeatAction(this);
final PresenterSelector controlPresenterSelector = ControlButtonPresenterSelector();
final PlaybackControlsRow playbackControlsRow = PlaybackControlsRow();
final ArrayObjectAdapter primaryAdapter = ArrayObjectAdapter(controlPresenterSelector);
final ArrayObjectAdapter secondaryAdapter = ArrayObjectAdapter(controlPresenterSelector);
// First row
primaryAdapter.add(skipPreviousAction);
primaryAdapter.add(rewindAction);
primaryAdapter.add(playPauseAction);
primaryAdapter.add(fastForwardAction);
primaryAdapter.add(skipNextAction);
// Second row
secondaryAdapter.add(repeatAction);
playbackControlsRow.primaryActionsAdapter = primaryAdapter;
playbackControlsRow.secondaryActionsAdapter = secondaryAdapter;
// Applying row to glue host
glueHost.setPlaybackRowPresenter(PlaybackControlsRowPresenter());
glueHost.setPlaybackRow(playbackControlsRow);
}
Now I have my customized playback row but the player no longer responds to button clicks.
I'm having a hard time trying to figure out how PlaybackGlue actually reacts to events from a PlaybackGlueHost. I know I can set OnActionClickedListener in a row presenter and handle actions manually but it kind of renders glue/host interaction model useless. How did it work in the first place? There is no default playback row I could find... I see that each Action corresponds to a set of key codes but I'm not sure what I can make of it.
Could anyone point me in the right direction? There's surprisingly little information on the subject.
Thanks.

Turned out PlaybackControlGlue had all the necessary methods (setControlsRow(), onCreatePrimaryActions(), onCreateSecondaryActions()) which I totally missed in the implementation I borrowed. The glue takes care of providing them to its host in onAttachedToHost() and then sets neccessary listeners.

Related

Can Android Presentation handle touch events?

I know that Android Presentations can have their own layouts which means I can create UI components like buttons etc. However, does anyone know if it is possible for Presentations to handle touch events?
I have tried adding a button on the Presentation layout and registering a button onClickListener but it seems to not be working. Is there another way?
Here is my code:
In my Presentation class
mHelloToast = (Button) findViewById(R.id.btn_presentation_hello_toast);
mHelloToast.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast toast = Toast.makeText(getContext(), "hello presentation", Toast.LENGTH_SHORT );
toast.show();
}
});
Edit: bump
Doc says:
A presentation is a special kind of dialog whose purpose is to present content on a secondary display. A Presentation is associated with the target Display at creation time and configures its context and resource configuration according to the display's metrics.
So I think Android Presention can handle touche event as Dialog.Here I will show you how Dialog handles touch event.
Step 1
Create your custom layout res/layout/dialog_signin.xml.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="#+id/btn_presentation_hello_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Step 2
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Get the layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
// Get the view which you want to receive touch event
//// Pass null as the parent view because its going in the dialog layout
View rootView = inflater.inflate(R.layout.dialog_signin, null);
Button bt = (Button)rootView.findViewById(R.id.btn_presentation_hello_toast);
// set click or touch listener
bt.setOnClickListener(....);
// set dialog's contents
builder.setView(rootView);

How to capture UIMediaController clicks via casting control interface

I've built a custom cast sender in my app that works perfectly - the only feature I need to add is a way to listen for the 'Play/Pause' and 'Skip' buttons, but Google's documentation (as far as I've been able to search for) doesn't give any clues, and after adding the functionality shown in Advanced Cast Features Docs did not work:
CastMediaOptions castMediaOptions = new CastMediaOptions.Builder()
.setMediaIntentReceiverClassName(CastIntentReceiver.class.getName())
.build();
return new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.cast_id))
.setCastMediaOptions(castMediaOptions)
.build();
I have a CastIntentReceiver that extends MediaIntentReceiver that's referenced in CastOptionsProvider and in the manifest.
public class CastIntentReceiver extends MediaIntentReceiver {
public static String TAG = "CastIntentReceiver";
public CastIntentReceiver() {
Log.i(TAG, "CastIntentReceiver: Constructed!");
}
...
// The rest of the Override methods don't do anything
}
The receiver is working, because the Log print is working, but none of the Override methods are called (no Log printing).
Here are some additional details on my implementation:
All casting functions are in two separate classes, the CastManager (controller) and the CastControls fragment (view) which is added to the base of the MainActivity View (similar to YouTube). There are also Interfaces that trigger things between these two classes.
In the CastManager, I setup the UIMediaController and bind Views:
View view = CastingBridge.CAST_CONTROLS_VIEW.findViewById(R.id.cast_drawer_controls);
uiMediaController = new UIMediaController(activity);
SeekBar seekBar = (SeekBar) view.findViewById(R.id.cast_control_seek_bar);
TextView start = (TextView) view.findViewById(R.id.cast_control_time_start);
TextView end = (TextView) view.findViewById(R.id.cast_control_time_end);
ImageButton playPauseButton = (ImageButton) view.findViewById(R.id.cast_control_play_pause);
ImageButton rewindButton = (ImageButton) view.findViewById(R.id.cast_control_rewind);
ImageButton forwardButton = (ImageButton) view.findViewById(R.id.cast_control_forward);
ImageButton stopButton = (ImageButton) view.findViewById(R.id.cast_control_stop);
ProgressBar loadingSpinner = (ProgressBar) view.findViewById(R.id.cast_control_loading);
As stated before, all these work as they should, but I need to listen for (capture) the touch/click events so that when a video is paused or stopped, I can save the current watch length for resume purposes.
How do I implement MediaIntentReceiver to listen for these events? I've tried adding click listeners to the individual Views, but as expected, #Override onClick removes the initial intended functionality.
The documentation specifies what seem to be the methods I need, but how is this referenced?
I also tried adding the only listener available to UIMediaController:
uiMediaController.setPostRemoteMediaClientListener(new RemoteMediaClient.Listener() {
#Override
public void onStatusUpdated() {
Log.i(TAG, "onStatusUpdated: Status Updated");
}
});
This, however does not do anything, as the onStatusUpdated method supplies no arguments.
Can anyone help me shed some light onto this? I've been trying different things and searching for a couple days now, so time to ask for help.
Thanks!
The receiver you have defined (and MediaIntentReceiver in general) is used to handle actions from notification, lockscreen and cast dialog; so having a custom one affects the behavior when it is initiated from those places. If you are using UIMediaController, then you'd need to use onSendingRemoteMediaRequest() to be notified when user interacts with these controls.

Espresso select children of included layout

I have been using Espresso to carry out automated UI testing with an Android app. (I have been trying to find a resolution to the issue whilst at home from work, so I don’t have the exact examples and errors, but I can update tomorrow morning). I have run into an issue with unit testing buttons within a layout that is included multiple times within a single user interface. Below is a quick example:
<include
android:id="#+id/include_one"
android:layout="#layout/boxes" />
<include
android:id="#+id/include_two"
android:layout="#layout/boxes" />
<include
android:id="#+id/include_three"
android:layout="#layout/boxes" />
Here is an example of what is within the #layout/boxes:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/button1" />
<Button
android:id="#+id/button2" />
</RelativeLayout>
I am seemingly unable to access button one within the include I want “include_one”, without accessing all three of the buttons.
I have tried accessing the buttons with the following:
onView(allOf(withId(R.id.include_one), isDescendantOfA(withId(R.id.button1)))).perform(click());
and
onView(allOf(withId(R.id.button1), hasParent(withId(R.id.include_one)))).perform(click());
Both of which I found from this answer: onChildView and hasSiblings with Espresso Unfortunately I haven’t had any success!
I know this isn’t great, but as I am not with my work computer I can’t tell you the exact errors I have come across, but I have encountered:
com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException
also an error telling me there were no matches found.
The code I am using makes sense, although I am new to using Espresso Can anyone offer some advice, or point out what I may be misunderstanding?
This is a common pitfall when trying to <include/> the same custom xml several times in the same layout.
If you now try calling
Button button1 = (Button) findViewById(R.id.button1);
since the boxes.xml is included more than once, you will always get as a result the button present in the first sub layout, and never another one.
You were pretty close but you need to use the withParent() view matcher.
onView(allOf(withId(R.id.button1), withParent(withId(R.id.include_one))))
.check(matches(isDisplayed()))
.perform(click());
I had similar issue, applied accepted answer but didn't work. Hereby I come across look into expected level of parent hierarchy
private static final class WithParentMatcher extends TypeSafeMatcher<View> {
private final Matcher<View> parentMatcher;
private int hierarchyLevel;
private WithParentMatcher(Matcher<View> parentMatcher, int hierarchyLevel) {
this.parentMatcher = parentMatcher;
this.hierarchyLevel = hierarchyLevel;
}
#Override
public void describeTo(Description description) {
description.appendText("has parent matching: ");
parentMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(View view) {
ViewParent viewParent = view.getParent();
for (int index = 1; index < hierarchyLevel; index++) {
viewParent = viewParent.getParent();
}
return parentMatcher.matches(viewParent);
}
}
Then create a helper method
public static Matcher<View> withParent(final Matcher<View> parentMatcher, int hierarchyLevel) {
return new WithParentMatcher(parentMatcher, hierarchyLevel);
}
Here is the usage
onView(allOf(withId(R.id.button1), withParent(withId(R.id.include_one), 2))).perform(click());

Can we embed button inside the min3d screen?

Is it possible to insert a button at the bottom of a working 3d animation done with Min3d framework in android. I do have a working 3d model car which is rotating. I want to put a button at the bottom of this working animation screen. Is it possible since i am not using any layout.Please explain me with an example so that i can replicate the same with my program.
Yes. Rather, the procedure is to rather add the min3d framework to the layout, using glSurfaceView. The following cocde highlights how to achieve the same, assuming you have the other basics (such as loading the object ) covered. If not, links for the same is provided at the end of this brief.
//import statements
public class threedviewActivity extends RendererActivity implements View.OnClickListener,View.OnTouchListener{
//onClick and onTouch in case you want to recognize both touch and click on the button
//initialize the variables you require
#Override
public void onCreateSetContentView() {
setContentView(R.layout.activity_example);
RelativeLayout R1 = (RelativeLayout)this.findViewById(R.id.SceneHolder);
R1.addView(_glSurfaceView); //adding the min3d view into the layout
Button example_1 = (Button)this.findViewById(R.id.example_1);
Button example_2 = (Button)this.findViewById(R.id.example_2);
example_1.setOnTouchListener(this);
example_2.setOnClickListener(this);
}
public boolean onTouch(View $v, MotionEvent event) {
switch($v.getId())
{
case R.id.example_1:
//Your actions and things to do
case R.id.example_2:
//your code
}
}
public void onClick(View $v)
{
//similar to above
}
public void initScene()
{
//initialize object and other activities related to the 3d container
}
#Override
public void updateScene()
{
//update effects such as rotation on the object here, based on the button click or touch, etc.
}
}
And the xml file containing the layout/activity should look something like this :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
//your data
android:id="#+id/example_1"
/>
<Button
//your data
android:id="#+id/example_2"
/>
<RelativeLayout
//nested layout which will hold the 3d container
android:id="#+id/SceneHolder">
</RelativeLayout>
</RelativeLayout>
for understanding more about working in initScene() function, you can refer here and here.
You can also add extra effects, by playing around with the lighting properties, as stated here
The basic example for adding into the layout was taken from here . The same page also provides links to many examples in case you want to work with in future.

Android, play a sound the moment a view is visible on screen

I am having trouble with audio in Android. Here is the deal, I have a very simple splash screen. My intention is once the screen loads it will play a very small audio file.
The problem is that a lot of the time, the audio will play before the splash screen actually appears.
Is there a way programmatically to verify that the screen has loaded? I do not want to add an unnecessary timer to make sure the sound doesn't play before it loads. And I want the sound to play at the exact moment the screen appears.
Here is a snippet from my xml file:
<ImageView
android:src="#drawable/splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
splash is a .png image
And here is the onCreate code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scarysplash);
mpPlaySound = MediaPlayer.create(this, R.raw.scream1);
mpPlaySound.start();
}
}
Thanks in advance for your help!
you are not the only only one who has similar problem:)) There are couple of ways how to do that, the popular one is to implement OnGlobalLayoutListener(), e.g.:
View yourView = ...;
yourView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener()
{
// -----------------------------------------
//
// Callback method to be invoked when the
// global layout state or the visibility
// of views within the view tree changes
//
// -----------------------------------------
#Override
public void onGlobalLayout()
{
// Remove view
View yourView = findViewById( R.id.yourId );
if ( yourView != null )
{
yourView .getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// PLAY SOUND
}
});
Petr

Categories

Resources