diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/events/ForceReloadLocalizationsEvent.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/events/ForceReloadLocalizationsEvent.java new file mode 100644 index 00000000..ec5450e3 --- /dev/null +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/events/ForceReloadLocalizationsEvent.java @@ -0,0 +1,4 @@ +package org.mbari.vars.ui.events; + +public class ForceReloadLocalizationsEvent { +} diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayer.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayer.java index 08d7c071..f4a312eb 100644 --- a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayer.java +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayer.java @@ -23,11 +23,16 @@ public class MediaPlayer extends org private final Runnable shutdownHook; private final Media media; - public MediaPlayer(Media media, ImageCaptureService imageCaptureService, VideoIO videoIO) { + public MediaPlayer(Media media, + ImageCaptureService imageCaptureService, + VideoIO videoIO) { this(media, imageCaptureService, videoIO, () -> {}); } - public MediaPlayer(Media media, ImageCaptureService imageCaptureService, VideoIO io, Runnable shutdownHook) { + public MediaPlayer(Media media, + ImageCaptureService imageCaptureService, + VideoIO io, + Runnable shutdownHook) { super(io); this.media = media; this.imageCaptureService = imageCaptureService; diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayers.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayers.java index 30788b16..9343c1c9 100644 --- a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayers.java +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/MediaPlayers.java @@ -2,6 +2,7 @@ import org.mbari.vars.core.EventBus; import org.mbari.vars.ui.UIToolBox; +import org.mbari.vars.ui.events.ForceReloadLocalizationsEvent; import org.mbari.vars.ui.events.MediaChangedEvent; import org.mbari.vars.ui.events.MediaControlsChangedEvent; import org.mbari.vars.ui.events.MediaPlayerChangedEvent; @@ -58,6 +59,7 @@ private void open(Media media) { // Close the old one synchronized (lock) { close(); + var eventBus = toolBox.getEventBus(); try { StreamUtilities.toStream(serviceLoader.iterator()) @@ -68,20 +70,21 @@ private void open(Media media) { .ifPresent(factory -> { try { var mediaControls = factory.safeOpen(media); - toolBox.getEventBus() - .send(new MediaControlsChangedEvent(MediaPlayers.this, + eventBus.send(new MediaControlsChangedEvent(MediaPlayers.this, mediaControls)); } catch (Exception e) { log.error("Unable to load services", e); - toolBox.getEventBus() - .send(new MediaPlayerChangedEvent(null, null)); + eventBus.send(new MediaPlayerChangedEvent(null, null)); } + // #174: Annotations are sometimes send before the media + // is ready. So this triggers a clear and reload in the + // OutgoingController + eventBus.send(new ForceReloadLocalizationsEvent()); }); } catch (ServiceConfigurationError e) { log.error("Unable to load services", e); - toolBox.getEventBus() - .send(new MediaPlayerChangedEvent(null, null)); + eventBus.send(new MediaPlayerChangedEvent(null, null)); } } } diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/IncomingController.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/IncomingController.java index bdeecbb7..680d01b0 100644 --- a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/IncomingController.java +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/IncomingController.java @@ -73,9 +73,7 @@ private void handleAdd(List added) { var annotation = localizedAnnotation.annotation(); var association = localizedAnnotation.association(); - // https://github.com/mbari-org/vars-annotation/issues/170 - remoteControl.getVideoIO().send(new RemoveLocalizationsCmd(remoteControl.getVideoIO().getUuid(), - List.of(loc.getUuid()))); + var videoIndex = new VideoIndex(localizedAnnotation.annotation().getElapsedTime()); var cmd = new CreateAnnotationAtIndexWithAssociationCmd(videoIndex, annotation.getConcept(), @@ -84,6 +82,15 @@ private void handleAdd(List added) { //LocalizationController.EVENT_SOURCE); #170 toolBox.getEventBus().send(cmd); + // Two issues here. The first is that we need to get the localization UUID to match the + // corresponding association UUID.So we just create a new localization and delete the original + // The second issue is that the localization was being removed too quickly. So we send the + // remove after the new one has been created (Yes order is important here.). + // https://github.com/mbari-org/vars-annotation/issues/170 + // https://github.com/mbari-org/vars-annotation/issues/174 + remoteControl.getVideoIO().send(new RemoveLocalizationsCmd(remoteControl.getVideoIO().getUuid(), + List.of(loc.getUuid()))); + }); } diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/MediaControlsFactoryImpl.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/MediaControlsFactoryImpl.java index 64ff018a..e3834273 100644 --- a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/MediaControlsFactoryImpl.java +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/MediaControlsFactoryImpl.java @@ -95,6 +95,7 @@ public CompletableFuture> open(Media media, int remo remoteControl.close(); incomingController.close(); outgoingController.close(); + sharktopodaState.setSelectedLocalizations(null); }); }); diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/OutgoingController.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/OutgoingController.java index b0f141b0..a1ce5fda 100644 --- a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/OutgoingController.java +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/OutgoingController.java @@ -4,10 +4,7 @@ import org.mbari.vars.core.EventBus; import org.mbari.vars.services.model.Annotation; import org.mbari.vars.ui.UIToolBox; -import org.mbari.vars.ui.events.AnnotationsAddedEvent; -import org.mbari.vars.ui.events.AnnotationsChangedEvent; -import org.mbari.vars.ui.events.AnnotationsRemovedEvent; -import org.mbari.vars.ui.events.AnnotationsSelectedEvent; +import org.mbari.vars.ui.events.*; import org.mbari.vars.ui.mediaplayers.sharktopoda.Constants; import org.mbari.vcr4j.remote.control.RVideoIO; import org.mbari.vcr4j.remote.control.commands.localization.*; @@ -38,32 +35,48 @@ public OutgoingController(UIToolBox toolBox, } private void init(EventBus eventBus) { - disposables.add(eventBus.toObserverable() + + var observable = eventBus.toObserverable(); + + disposables.add(observable .ofType(AnnotationsAddedEvent.class) .filter(evt -> evt.getEventSource() != Constants.LOCALIZATION_EVENT_SOURCE) .filter(evt -> !evt.get().isEmpty()) .subscribe(evt -> handle(evt.get(), Action.Add))); - disposables.add(eventBus.toObserverable() + disposables.add(observable .ofType(AnnotationsRemovedEvent.class) .filter(evt -> evt.getEventSource() != Constants.LOCALIZATION_EVENT_SOURCE) .filter(evt -> !evt.get().isEmpty()) .subscribe(evt -> handle(evt.get(), Action.Remove))); - disposables.add(eventBus.toObserverable() + disposables.add(observable .ofType(AnnotationsChangedEvent.class) .filter(evt -> evt.getEventSource() != Constants.LOCALIZATION_EVENT_SOURCE) .filter(evt -> !evt.get().isEmpty()) .subscribe(evt -> handle(evt.get(), Action.Update))); - disposables.add(eventBus.toObserverable() + disposables.add(observable .ofType(AnnotationsSelectedEvent.class) .filter(evt -> evt.getEventSource() != Constants.LOCALIZATION_EVENT_SOURCE) .subscribe(evt -> handle(evt.get(), Action.Select))); + + // #174: Force reload localizations in the video player + disposables.add(observable + .ofType(ForceReloadLocalizationsEvent.class) + .subscribe(evt -> forceReload())); + } + + /** + * Clear and re-send the localizations to the video player + */ + private void forceReload() { + io.send(new ClearLocalizationsCmd(new ClearLocalizationsCmd.Request(io.getUuid()))); + handle(toolBox.getData().getAnnotations(), Action.Add); } private void handle(Collection annotations, Action action) { - var media = toolBox.getData().getMedia(); +// var media = toolBox.getData().getMedia(); List localizations = LocalizedAnnotation.from(annotations) .stream() .flatMap(opt -> opt.toLocalization(toolBox).stream()) diff --git a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/SharktopodaState.java b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/SharktopodaState.java index 26512cde..9771659a 100644 --- a/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/SharktopodaState.java +++ b/org.mbari.vars.ui/src/main/java/org/mbari/vars/ui/mediaplayers/sharktopoda2/SharktopodaState.java @@ -1,30 +1,34 @@ package org.mbari.vars.ui.mediaplayers.sharktopoda2; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.locks.ReentrantLock; public class SharktopodaState { - private final List selectedLocalizations = new CopyOnWriteArrayList<>(); + private final Set selectedLocalizations = new CopyOnWriteArraySet<>(); + private final ReentrantLock lock = new ReentrantLock(); public void setSelectedLocalizations(Collection selectedLocalizations) { - synchronized (this.selectedLocalizations) { + lock.lock(); this.selectedLocalizations.clear(); - this.selectedLocalizations.addAll(selectedLocalizations); - } + if (selectedLocalizations != null) { + this.selectedLocalizations.addAll(selectedLocalizations); + } + lock.unlock(); } public boolean isDifferentThanSelected(Collection localizations) { +// var selected = new HashSet<>(selectedLocalizations); +// var copy = new HashSet<>(localizations); +// return !selected.equals(copy); var isDiff = true; if (!localizations.isEmpty() && !selectedLocalizations.isEmpty()) { - var copy = new ArrayList<>(localizations); - var copySelected = new ArrayList<>(selectedLocalizations); + var copy = new HashSet<>(localizations); + var copySelected = new HashSet<>(selectedLocalizations); if (copy.size() == copySelected.size()) { copy.removeAll(copySelected); - isDiff = copy.size() != 0; + isDiff = !copy.isEmpty(); } } return isDiff;