Is LifecycleRegistry's handleLifecycleEvent giving me the wrong event callback? What changed? - java

Up until a few weeks ago, my LifeCycleOwnerAwareObserver class was working fine.
It was design with the purpose of self detach on Destroy.
#Override
public void onStateChanged(#NonNull LifecycleOwner source, #NonNull Lifecycle.Event event) {
Log.d(TAG, "onStateChanged: event is: " + event.name());
Lifecycle.State state = source.getLifecycle().getCurrentState();
Log.println(Log.WARN, TAG, "onStateChanged: state is: " + state.name());
if (state.isAtLeast(INITIALIZED) && !state.equals(DESTROYED)) {
observer.get().accept(event);
} else if (state.equals(DESTROYED)) {
observer.get().accept(event);
observer.set(() -> null);
source.getLifecycle().removeObserver(this);
}
}
The idea was to build lifeCycle aware components to handle automatic unregistering.
90% of my project relies on this components...
I have not perceived any change, specially on Adapters, which listen to Fragments, the only a weird behavior I saw, where onViewCreated (an ON_START callback attaches an observer to the fragment's LifeCycleOwnerLiveData) was triggering slightly after the real onViewCreated(), but ONLY when coming back from the backStack... This is not good at all, but with some precautions it can be somewhat ignored.
But then this was the weirdest of them all...
I have a custom view (ViewParticle.class) with its own LifeCycle that implements a LifeCycleRegistry.
This code was working a few weeks ago... since I have not been testing everything constantly, I am not sure at which moment this stopped working here is the code:
private final MyLifeCycleOwner mViewLifecycleOwner = new MyLifeCycleOwner();
#Override
public void viewDestroyed() {
Lifecycle.Event event = Lifecycle.Event.ON_DESTROY;
Log.d(TAG, "viewDestroyed: event is: " + event.toString());
mViewLifecycleOwner.handleLifecycleEvent(event);
}
The receiving end:
#Override
public void viewPrepared() {
lifecycleSupplier = () -> mViewLifecycleOwner;
Lifecycle lCRef = mViewLifecycleOwner.getLifecycle();
//The callback HERE!!
lCRef.addObserver(
new LifeCycleOwnerAwareObserver(
event -> {
Log.d(TAG, "viewPrepared: event is: " + event.name());
if (event.equals(Lifecycle.Event.ON_DESTROY)) lastResponseProvider.run();
}
)
);
lifeCycleProvider.run();
mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
The Logs are showing this when viewDestroyed() is executed:
D/ViewParticle: viewDestroyed: event is: ON_DESTROY
D/MyLifeCycleOwner: handleLifecycleEvent: event is: ON_DESTROY
D/LifeCycleOwnerAwareObse: onStateChanged: event is: ON_STOP
W/LifeCycleOwnerAwareObse: onStateChanged: state is: DESTROYED
D/ViewParticle: viewPrepared: event is: ON_STOP
As you can see the Event.ON_DESTROY enum is translating into:
a) Lifecycle.State.DESTROYED
b) Lifecycle.Event.ON_STOP
Which is impossible because the getStateAfter() method is as:
static State getStateAfter(Event event) {
switch (event) {
case ON_CREATE:
case ON_STOP:
return CREATED;
case ON_START:
case ON_PAUSE:
return STARTED;
case ON_RESUME:
return RESUMED;
case ON_DESTROY:
return DESTROYED;
case ON_ANY:
break;
}
throw new IllegalArgumentException("Unexpected event value " + event);
}
Which means an Event will never differ from an State, because an State IS the product of an Event, AND what triggers/begins the callback is the Event, NOT the STATE.
This means that if the State is Destroyed, the Event MUST be ON_DESTROYED.
I cannot explain what's happening here..

I won't look too much into the issue, but at first glance the answer seems to be here:
while (!isSynced()) {
mNewEventOccurred = false;
// no need to check eldest for nullability, because isSynced does it for us.
if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
backwardPass(lifecycleOwner);
}
Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
forwardPass(lifecycleOwner);
}
}
The backwardPass(lifecycleOwner); and forwardPass(lifecycleOwner); methods, seem to work under the assumption that the mState.compareTo(newest.getValue().mState) > 0 and the mState.compareTo(mObserverMap.eldest().getValue().mState) < 0 conditions will never be greater than 1, so even if the answer is "true because it is 2", the forwardPass() method will only advance a single node in the life cycle, starting from the one of its previous value...
This behavior makes the function of the getStateAfter(Event event) method pointless.
BTW I remember this specifically giving me problems on this line:
mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START);
Of my code above, this means that this in fact gave me problems before, So now the weird thing that needs clarification is why was it working fine in the first place? IDK.
The Answer is this:
#Override
public void viewDestroyed() {
Lifecycle.Event event = Lifecycle.Event.ON_STOP;
Log.d(TAG, "viewDestroyed: event is: " + event.toString());
mViewLifecycleOwner.handleLifecycleEvent(event);
Lifecycle.Event event2 = Lifecycle.Event.ON_DESTROY;
Log.d(TAG, "viewDestroyed: event is: " + event.toString());
mViewLifecycleOwner.handleLifecycleEvent(event2);
}
The requirement is that one needs to traverse the enums in order, up to the point of reaching what's desired.
public enum Event {
ON_CREATE,
ON_START,
ON_RESUME,
ON_PAUSE,
ON_STOP,
ON_DESTROY,
ON_ANY
}
It seems, as of now, that the only skippable enums are:
ON_RESUME and ON_PAUSE

Related

Reset checkbox selection in ChangeListener under condition

In my JavaFX application I'm using Checkboxes in a TreeView to change the visibility of nodes.
checkbox selected = some nodes are visible
checkbox deselected = some nodes are invisible
In a special case, however, the user should be prompted to confirm their selection, because problems can arise when activating a specific checkbox.
A dialog window opens in which the user can choose between "Yes" and "No".
If the user chooses "Yes", nodes become visible and everything is fine.
But if the user chooses "No", the checkbox should be deselected again.
My idea was to check the condition (in this case press "no" in a dialog window) in the ChangeListener and if it's true, set the selected value to false.
But for whatever reason, it didn't work. After that, I figured out that it works with the refresh() method of the TreeView.
Questions
Why doesn't it work without the refresh() method, why setSelected() is ignored?
Should I use the refresh() method?
Is there a better workaround to change the selection status?
Minimal reproducible example
Using the refresh() line will show the desired behavior: The checkbox remains unselected after clicking because 5 > 4 (5>4 simulates for example pressing "no" in a dialog window).
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.CheckTreeView;
public class HelloApplication extends Application {
enum Names { TEST1, TEST2, TEST3, TEST4 }
private final CheckTreeView<String> checkTreeView = new CheckTreeView<>();
#Override
public void start(Stage stage) {
VBox vBox = new VBox();
Scene scene = new Scene(vBox, 500, 500);
setTreeView();
vBox.getChildren().add(checkTreeView);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public void setTreeView() {
CheckBoxTreeItem<String> rootItem = new CheckBoxTreeItem<>("Root");
rootItem.setExpanded(true);
for (Names name : Names.values()) {
CheckBoxTreeItem<String> item = new CheckBoxTreeItem<>(name.toString(), null);
item.selectedProperty().addListener(this.onSelectionChanged(item));
rootItem.getChildren().add(item);
}
this.checkTreeView.setRoot(rootItem);
}
private ChangeListener<Boolean> onSelectionChanged(CheckBoxTreeItem<String> item) {
return (observableValue, previousChoice, newChoice) -> {
if (newChoice) { // if checkbox is selected
// if anything happens... for example press a "no" button in a dialog window
if (5 > 4) {
System.out.println("reset checkbox status");
item.setSelected(previousChoice);
}
}
// it works with refresh:
// this.checkTreeView.refresh();
};
}
}
EDIT:
The solution of #VGR with
Platform.runLater(() -> item.setSelected(previousChoice));
works for the minimal reproducible example, but it doesn't seem to be the best way to do that.
As already discussed in the comments, with Platform.runLater() it's
working most of the time
and
there's no guarantee because the exact timing is unspecified, will break f.i. if something in the pending events does also use runlater.
A solution / workaround that always works is desired...
What we are seeing in the OPs example actually is the standard behavior of bidirectionally bound properties, reduced to a simple (no ui, just properties) example.
The scenario:
there are two boolean properties, representing the data (checkBoxTreeItem) and ui (checkBox) selected properties
they are wired the same way as in a checkBoxTreeCell, that is the ui is bidirectionally bound to the data
a listener to the data reverts the data to false on receiving a change from false -> true
Just for showing that - though nor recommended - reverting a the state of the sender in a listener is working: we set the data property to true. At the end, both data and ui are false as we want it.
before data.set(true)
state of data/ui: false / false
enter listener to ui: true
enter listener to data: true
enter listener to ui: false
enter listener to data: false
... returning
listener to data - after reset false
after data.set(true) - state of data/ui: false / false
For the real simulation of the OP's context: set the ui property (user clicks the checkBox) - at the end, the data property is reverted while the ui property is not. Technically, that's done in BidirectionalBinding which ignores incoming changes while updating.
before ui.set(true)
state of data/ui: false / false
enter listener to data: true
enter listener to data: false
... returning
listener to data - after reset false
enter listener to ui: true
after ui.set(true) - state of data/ui: false / true
The example:
public class BidiBindingUpdating {
public static void main(String[] args) {
BooleanProperty data = new SimpleBooleanProperty();
BooleanProperty ui = new SimpleBooleanProperty();
ui.bindBidirectional(data);
// listener to item: revert to false
data.addListener((src, ov, nv) -> {
System.out.println("enter listener to data: " + nv);
if (!nv) {
System.out.println(" ... returning");
return;
}
data.set(ov);
System.out.println("listener to data - after reset " + data.get());
});
// listener to check: logging only
ui.addListener((src, ov, nv) -> {
System.out.println("enter listener to ui: " + nv);
});
// set item directly:
System.out.println("before data.set(true)");
System.out.println(" state of data/ui: " + data.get() + " / " + ui.get());
data.set(true);
System.out.println("after data.set(true) - state of data/ui: " + data.get() + " / " + ui.get());
// set bound property:
System.out.println("\nbefore ui.set(true)");
System.out.println(" state of data/ui: " + data.get() + " / " + ui.get());
ui.set(true);
System.out.println("after ui.set(true) - state of data/ui: " + data.get() + " / " + ui.get());
}
}
You are resetting the checkbox item too soon. It hasn’t finished its event processing yet.
You want to wait until it’s finished before you reset it. Use Platform.runLater to make sure you reset it after all pending events have been processed:
Platform.runLater(() -> item.setSelected(previousChoice));
I believe you got all the information about the issue from the previous answers and the comments in this post. So I will not repeat explaining the cause for the issue.
I can suggest one alternate workaround which is similar to #VGR's answer of using Platform.runLater. Though Platform.runLater acts like a master key to most of the issues, mostly it is not recommended regarding its unpredictability and the threading reasons.
The key reason for issue as mentioned in #VGR answer :
You are resetting the checkbox item too soon. It hasn’t finished its
event processing yet. You want to wait until it’s finished before you
reset it.
and in #kelopatra comment:
I think the culprit is the reverting of item's selected value in the
listener
From the above reasons it looks like we can fix the issue, if we run the desired code at the end of the event processing (I mean after all the layout children is finished).
JavaFX has a very powerful class AnimationTimer. You can go through the API of this class, but in simple words, it will allow you to run your desired code at the end of every frame. In constrast to Platform.runLater, here we know when it is going to run. Infact usage of this class is actually recommened in Scene -> addPostLayoutPulseListener API.
Solution:
Create a utility class that accepts a Runnable and runs that code only ONCE at the end of the current scene pulse. By the time this code is executed, the layouting in the scene is already completed, so you will not have any issues.
import javafx.animation.AnimationTimer;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Utility to run the provided runnable in each scene pulse.
*/
public final class PulseUtil extends AnimationTimer {
/** Specifies the count of current pulse. */
private static final int CURRENT_PULSE = 0;
/** Specifies the count of the timer once it is started. */
private final AtomicInteger count = new AtomicInteger(0);
/** Specifies the order at which the runnable need to be executed. */
private final int order;
/** Runnable to execute in each frame. */
private final Runnable callback;
/**
* Constructor.
*
* #param aCallBack runnable to execute
* #param aOrder order at which the runnable need to be executed
*/
private PulseUtil(final Runnable aCallBack, final int aOrder) {
callback = aCallBack;
order = aOrder;
}
/**
* Executes the provided runnable at the end of the current pulse.
*
* #param callback runnable to execute
*/
public static void doEndOfPulse(final Runnable callback) {
new PulseUtil(callback, CURRENT_PULSE).start();
}
#Override
public void handle(final long now) {
if (count.getAndIncrement() == order) {
try {
callback.run();
} finally {
stop();
}
}
}
}
And you will call this method in the change listener:
private ChangeListener<Boolean> onSelectionChanged(CheckBoxTreeItem<String> item) {
return (observableValue, previousChoice, newChoice) -> {
if (newChoice) { // if checkbox is selected
// if anything happens... for example press a "no" button in a dialog window
if (5 > 4) {
System.out.println("reset checkbox status");
PulseUtil.doEndOfPulse(()->item.setSelected(previousChoice));
}
}
};
}

Wicket's AjaxSelfUpdatingTimerBehavior stops updating after leaving and re-entering the page

I have this behavior added to a component(MarkupContainer)
AjaxSelfUpdatingTimerBehavior updateBehavior = new AjaxSelfUpdatingTimerBehavior(Duration.seconds(3))
{
#Override
public void onEvent(Component component, IEvent<?> event) {
// some business logic
}
};
Somewhere , on the same page I have an AjaxLink which redirects to another page(in whom constructor I pass the actual page as a parameter) and on that page I have a "Back" AjaxLink which redirects me back , calling setResponsePage(myFirstPage) .
The problem is that even though , when rendering the page the behavior updates once , it stops updating once at 3 seconds , as was constructed for.No problem faced with the behavior until leaving the page.
Probably not the best solution , but I managed to fix it by removing the behavior onBeforeRender() of the page and adding again . I declared a field on the page private int autoUpdateBehaviorId = -1;
public void addUpdateBehavior(Component c)
{
if(autoUpdateBehaviorId >= 0)
c.remove(c.getBehaviorById(autoUpdateBehaviorId));
AjaxSelfUpdatingTimerBehavior updateBehavior = new AjaxSelfUpdatingTimerBehavior(Duration.seconds(3))
{
#Override
public void onEvent(Component component, IEvent<?> event) {
// bussines logic
}
};
c.add(updateBehavior);
autoUpdateBehaviorId = c.getBehaviorId(updateBehavior);
}
#Override
protected void onBeforeRender() {
super.onBeforeRender();
addUpdateBehavior(myContainer);
}
Not necessarily the solution to your problem; but I have implemented the behavior by overriding onConfigure method of the AjaxSelfUpdatingTimerBehavior as below.
In my case, I had to update label with a count of current records in queue every 10 seconds.
Following is code snippet:
labelToBeUpdated.add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(configurableDelay)) {
#Override
public void onConfigure(Component component) {
String inProgressOutOfTotal = "10/100"; //Business logic to get total count and inprogress count
labelToBeUpdated.setDefaultModel(Model.of(inProgressOutOfTotal));
//Set visibility of the component if needed
}
}
labelToBeUpdated.setOutputMarkupId(true);
Just curious; is it that onEvent is waiting on an event on the component in order to refresh? Since onConfigure is called before the rendering cycle has begun, it is working for me.
But as Sven Meier has mentioned, you might still want to work on his advise to get your code with onEvent.

Java code to handle node failure in jgroups cluster

My Jgroups config file contains the protocol/config
<FD timeout="3000" max_tries="3" />
But how do I use this in the Java code. For example, if there is a cluster and when I detect a failure I want to call an external notifier service via a REST call, like /nodeDown/nodeID
I'm not able to find any java code which does this, all I see is message receive and send, is there a way I can implement this?
Thanks
Adding some more info
I have done the step of writing a RecieverAdpater and override the start, stop, send, recieve method. Please find some code here,
public void receive(Message msg) {
JGroupsDataPacket pckt = (JGroupsDataPacket) msg.getObject();
if ( pckt.getCmd().equals("cacheUpdate") ){
int uid = pckt.getAffectedUid();
cacheUpdateRoutine(uid);
}
if ( pckt.getCmd().equals("ack") ){
System.out.println("got the mesaage!");
}
logger.log(LogLevel.ERROR, "received msg from " + msg.getSrc() + ": " + msg.getObject());
}
public void send(JGroupsDataPacket pckt){
Message msg = new Message(null, null, pckt);
msg.setFlag(Message.Flag.RSVP);
try {
channel.send(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
I want to know where should I add code for example to handle the TimeOutException when I'm sending a message with the RSVP flag enabled. Another requirement is to know, which is the Java callback method which is called when SUSPECT(P) is triggered. I want to catch and handle the machine's going down, timout etc.
Is the viewAccepted() the only place where I can handle this? Is there a sample code around this?
Also is http://www.jgroups.org/manual/html/user-channel.html
the section 3. APIs give all java/programmatic things we can do with JGroups.
Thanks again
I found some documentation here, I think this is the class which I'm supposed to override
public interface MembershipListener {
void viewAccepted(View new_view);
void suspect(Object suspected_mbr);
void block();
void unblock();
}
OK, first off, you have a JChannel. You need to use it to register for view callbacks, like this:
JChannel ch;
ch.setReceiver(this);
'this' extends ReceiverAdapter and overrides viewAccepted():
public void viewAccepted(View view) {
// handle new view
}
To determine the members which left between views v1 and v2:
List<Address> left_mbrs=View.leftMembers(v1,v2);

Blackberry | keyUp and keyDown methods from MediaKeyListener not called when app in background

I want to handle long press (about 10sec) of volume button (volume up or volume down doesn't matter).
For this reason I used own implementation of MediaKeyListener
private class LoongPressKeyListener extends MediaKeyListener {
public boolean mediaAction(int action, int source,
Object paramObject) {
System.out.println("::: action=" + action + ";source=" + source + ";object=" + paramObject);
}
public boolean keyDown(int keycode, int status) {
switch (Keypad.key(keycode)) {
case Keypad.KEY_VOLUME_UP:
case Keypad.KEY_VOLUME_DOWN:
System.out.println("volume keyDown");
return true;
default:
return super.keyDown(keycode, status);
}
}
public boolean keyUp(int keycode, int status) {
switch (Keypad.key(keycode)) {
case Keypad.KEY_VOLUME_UP:
case Keypad.KEY_VOLUME_DOWN:
System.out.println("volume keyUp");
return true;
default:
return super.keyUp(keycode, status);
}
}
}
But for some reason keyUp and keyDown method have been called only when application in foreground. mediaAction has been called only when app in background.
Can someone explain is this a correct behavior? And is it possible to handle long volume button press from background?
What you're seeing is correct behaviour, but you can still get volume up/down events while in the background. From the BlackBerry documentation for MediaKeyListener (bold is mine):
Applications would create a concrete subclass of this class that
implements the methods defined in MediaActionHandler. Then, an
instance of that class can be registered as a KeyListener either by
using Application.addKeyListener() or Screen.addKeyListener(), which
will handle media key presses while the application is in the
foreground. Then, the same instance of MediaKeyListener can be
registered using Application.addMediaActionHandler() in order to
respond to media key presses while in the background, as well as media
actions from other sources.
So, you need to make sure to call Application#addMediaActionHandler(), to add your LoongPressKeyListener instance.
Also, from the MediaActionHandler class API docs:
Media Actions from Background Key Presses
If a MediaActionHandler is registered via
Application.addMediaActionHandler() then it will be notified of media
key presses that occur while the application is in the background. If
the application that happens to be in the foreground consumes a media
key press then it will not be posted as a media action to media action
handlers. Invocations of mediaAction() resulting from background key
presses will have source==MediaActionHandler.SOURCE_BACKGROUND_KEY and
context==null.
Here is a sample for detecting volume up, volume down, and volume long presses while in the background:
public class MediaKeyHandler extends MediaKeyListener {
public MediaKeyHandler() {
// use this to register for events while in the background:
UiApplication.getUiApplication().addMediaActionHandler(this);
// use this to register for events while in the foreground:
//UiApplication.getUiApplication().addKeyListener(this);
}
public boolean mediaAction(int action, int source, Object context) {
System.out.println("mediaAction(" + action + "," + source + ",context)");
if (source == MediaActionHandler.SOURCE_BACKGROUND_KEY) {
switch (action) {
case MediaActionHandler.MEDIA_ACTION_VOLUME_DOWN:
// handle volume down key pressed
break;
case MediaActionHandler.MEDIA_ACTION_VOLUME_UP:
// handle volume up key pressed
break;
case MediaActionHandler.MEDIA_ACTION_NEXT_TRACK:
// handle volume up key long-pressed
break;
case MediaActionHandler.MEDIA_ACTION_PREV_TRACK:
// handle volume down key long-pressed
break;
default:
break;
}
return true; // keypress consumed
} else {
// keypresses while app is in foreground come here
return false; // keypress not consumed
}
}
Note: I know of no way to detect a 10-second volume key press while in the background. In the foreground, both KeyListener#keyRepeat() and MediaKeyListener#mediaAction() get called repeatedly, as you hold the key, but not in the background. The code I show above works with a long-press of about a second (not 10 seconds).

UPNP/DLNA Control Point

I'm working on an android UPnP/DLNA app. I have a control point working where I can stream files from media server to renderer. I can pause/play and stop the file during playback but I cannot seem to figure out how to integrate a seekbar into the control point to show the progress of the playing file and be able to interact with the seekbar. I am using the Cling Java library to create the app. If anyone has any examples that could help me I would really appreciate it.
Thanks
I've tried to implement the SubscriptionCallback example and subscribe to LastChange
SubscriptionCallback callback = new SubscriptionCallback(service, 600) { // Timeout in seconds
public void established(GENASubscription sub) {
System.out.println("Established: " + sub.getSubscriptionId());
}
#Override
public void failed(GENASubscription sub, UpnpResponse response, Exception ex) {
System.err.println(
createDefaultFailureMessage(response, ex)
);
}
#Override
public void ended(GENASubscription sub, CancelReason reason, UpnpResponse response) {
// Reason should be null, or it didn't end regularly
}
public void eventReceived(GENASubscription sub) {
System.out.println("Event: " + sub.getCurrentSequence().getValue());
try {
lastChange = new LastChange(
new AVTransportLastChangeParser(),
sub.getCurrentValues().get("LastChange").toString()
);
} catch (Exception ex) {
log.warning("Error parsing LastChange event content: " + ex);
return;
}
Map<String, StateVariableValue> values = sub.getCurrentValues();
StateVariableValue status = values.get("Status");
System.out.println("Status is: " + status.toString());
}
public void eventsMissed(GENASubscription sub, int numberOfMissedEvents) {
System.out.println("Missed events: " + numberOfMissedEvents);
}
#Override
protected void failed(GENASubscription arg0,
UpnpResponse arg1, Exception arg2, String arg3) {
}
};
upnpService.getControlPoint().execute(callback);
Then I try to get the duration of the current playing track:
System.out.println("Duration: "+lastChange.getEventedValue(0, AVTransportVariable.CurrentTrackDuration.class).getValue());
but this returns a NullPointerException.
Any ideas???????
************UPDATE***********
I have been trying to implement Seek() but have not had success.
I have my seekbar and listener but it it keeps failing when I drag the seekbar to a new position.
seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener()
{
#Override
public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2)
{
Log.i("SEEKTIME", "time:" + arg1);
upnpService.getControlPoint().execute(new Seek(service, SeekMode.REL_TIME, arg0.toString())
{
#Override
public void success(ActionInvocation invocation)
{
//super.success(invocation);
Log.i("SEEKSUCCESS", "success seek");
}
#Override
public void failure(ActionInvocation arg0, UpnpResponse arg1, String arg2)
{
Log.i("SEEKFAIL", "fail seek");
}
});
}
Any suggestions why this would be failing
You must poll the renderer for this kind of information (see AVTransport spec chapter 2.3.1). The spec encourages polling every second, but you can easily jam up a real hardware renderer (for which DLNA is still rather a fashionable pain in the a** than a vital part of the design). Our established practice is to send GetPositionInfo() request every 2-3 seconds and treat the returned RelativeTimePosition value only as an adjustment to locally running timer. For the seekbar sizing you also need the total length of current media. Ideally the renderer will tell you automatically when you subscribe to AVTransport.LastChange. I don't know Cling specifically but a quick look shows promising example in controlpoint.SubscriptionCallback. Unfortunately with real devices, LastChange often doesn't tell you anything much. Either the values are not there at all or have a constant inert value. So you will need to poll the GetMediaInfo() again and use MediaDuration value.
As for interaction, Seek() is your friend, ideally with parameters of Unit = REL_TIME and Target = your desired time offset. Be aware that a real world renderer may not be supporting this unit (mode) of seeking. Perhaps it supports only TRACK_NR in which case the seekbar is essentialy read-only for you. Again Cling should be able to tell you allowed values of A_ARG_TYPE_SeekMode for the particular renderer.

Categories