浏览代码

Add model for (remote) call participants

Clients that modify the model would define the variables using the
mutable subclass, while clients that only need to access the model are
expected to use the read-only base class.

The read-only class provides an observer; as it is expected that the
model will be modified from background threads but observed from the
main thread the observer can be registered along a handler to be
notified on its thread, independently of on which thread the values were
set.

Currently there does not seem to be a need to observe each value on its
own, so the observer is notified in a coarse way when any value changes.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Daniel Calviño Sánchez 2 年之前
父节点
当前提交
d72648379e

+ 164 - 0
app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java

@@ -0,0 +1,164 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Daniel Calviño Sánchez
+ * Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.call;
+
+import android.os.Handler;
+
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+
+import java.util.Objects;
+
+/**
+ * Read-only data model for (remote) call participants.
+ *
+ * The received audio and video are available only if the participant is sending them and also has them enabled.
+ * Before a connection is established it is not known whether audio and video are available or not, so null is returned
+ * in that case (therefore it should not be autoboxed to a plain boolean without checking that).
+ *
+ * Audio and video in screen shares, on the other hand, are always seen as available.
+ *
+ * Clients of the model can observe it with CallParticipantModel.Observer to be notified when any value changes.
+ * Getters called after receiving a notification are guaranteed to provide at least the value that triggered the
+ * notification, but it may return even a more up to date one (so getting the value again on the following
+ * notification may return the same value as before).
+ */
+public class CallParticipantModel {
+
+    public interface Observer {
+        void onChange();
+    }
+
+    protected class Data<T> {
+
+        private T value;
+
+        public T getValue() {
+            return value;
+        }
+
+        public void setValue(T value) {
+            if (Objects.equals(this.value, value)) {
+                return;
+            }
+
+            this.value = value;
+
+            callParticipantModelNotifier.notifyChange();
+        }
+    }
+
+    private final CallParticipantModelNotifier callParticipantModelNotifier = new CallParticipantModelNotifier();
+
+    protected final String sessionId;
+
+    protected Data<String> userId;
+    protected Data<String> nick;
+
+    protected Data<PeerConnection.IceConnectionState> iceConnectionState;
+    protected Data<MediaStream> mediaStream;
+    protected Data<Boolean> audioAvailable;
+    protected Data<Boolean> videoAvailable;
+
+    protected Data<PeerConnection.IceConnectionState> screenIceConnectionState;
+    protected Data<MediaStream> screenMediaStream;
+
+    public CallParticipantModel(String sessionId) {
+        this.sessionId = sessionId;
+
+        this.userId = new Data<>();
+        this.nick = new Data<>();
+
+        this.iceConnectionState = new Data<>();
+        this.mediaStream = new Data<>();
+        this.audioAvailable = new Data<>();
+        this.videoAvailable = new Data<>();
+
+        this.screenIceConnectionState = new Data<>();
+        this.screenMediaStream = new Data<>();
+    }
+
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    public String getUserId() {
+        return userId.getValue();
+    }
+
+    public String getNick() {
+        return nick.getValue();
+    }
+
+    public PeerConnection.IceConnectionState getIceConnectionState() {
+        return iceConnectionState.getValue();
+    }
+
+    public MediaStream getMediaStream() {
+        return mediaStream.getValue();
+    }
+
+    public Boolean isAudioAvailable() {
+        return audioAvailable.getValue();
+    }
+
+    public Boolean isVideoAvailable() {
+        return videoAvailable.getValue();
+    }
+
+    public PeerConnection.IceConnectionState getScreenIceConnectionState() {
+        return screenIceConnectionState.getValue();
+    }
+
+    public MediaStream getScreenMediaStream() {
+        return screenMediaStream.getValue();
+    }
+
+    /**
+     * Adds an Observer to be notified when any value changes.
+     *
+     * @param observer the Observer
+     * @see CallParticipantModel#addObserver(Observer, Handler)
+     */
+    public void addObserver(Observer observer) {
+        addObserver(observer, null);
+    }
+
+    /**
+     * Adds an observer to be notified when any value changes.
+     *
+     * The observer will be notified on the thread associated to the given handler. If no handler is given the
+     * observer will be immediately notified on the same thread that changed the value; the observer will be
+     * immediately notified too if the thread of the handler is the same thread that changed the value.
+     *
+     * An observer is expected to be added only once. If the same observer is added again it will be notified just
+     * once on the thread of the last handler.
+     *
+     * @param observer the Observer
+     * @param handler a Handler for the thread to be notified on
+     */
+    public void addObserver(Observer observer, Handler handler) {
+        callParticipantModelNotifier.addObserver(observer, handler);
+    }
+
+    public void removeObserver(Observer observer) {
+        callParticipantModelNotifier.removeObserver(observer);
+    }
+}

+ 86 - 0
app/src/main/java/com/nextcloud/talk/call/CallParticipantModelNotifier.java

@@ -0,0 +1,86 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Daniel Calviño Sánchez
+ * Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.call;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Helper class to register and notify CallParticipantModel.Observers.
+ *
+ * This class is only meant for internal use by CallParticipantModel; observers must register themselves against a
+ * CallParticipantModel rather than against a CallParticipantModelNotifier.
+ */
+class CallParticipantModelNotifier {
+
+    /**
+     * Helper class to associate a CallParticipantModel.Observer with a Handler.
+     */
+    private static class CallParticipantModelObserverOn {
+        public final CallParticipantModel.Observer observer;
+        public final Handler handler;
+
+        private CallParticipantModelObserverOn(CallParticipantModel.Observer observer, Handler handler) {
+            this.observer = observer;
+            this.handler = handler;
+        }
+    }
+
+    private final List<CallParticipantModelObserverOn> callParticipantModelObserversOn = new ArrayList<>();
+
+    public synchronized void addObserver(CallParticipantModel.Observer observer, Handler handler) {
+        if (observer == null) {
+            throw new IllegalArgumentException("CallParticipantModel.Observer can not be null");
+        }
+
+        removeObserver(observer);
+
+        callParticipantModelObserversOn.add(new CallParticipantModelObserverOn(observer, handler));
+    }
+
+    public synchronized void removeObserver(CallParticipantModel.Observer observer) {
+        Iterator<CallParticipantModelObserverOn> it = callParticipantModelObserversOn.iterator();
+        while (it.hasNext()) {
+            CallParticipantModelObserverOn observerOn = it.next();
+
+            if (observerOn.observer == observer) {
+                it.remove();
+
+                return;
+            }
+        }
+    }
+
+    public synchronized void notifyChange() {
+        for (CallParticipantModelObserverOn observerOn : new ArrayList<>(callParticipantModelObserversOn)) {
+            if (observerOn.handler == null || observerOn.handler.getLooper() == Looper.myLooper()) {
+                observerOn.observer.onChange();
+            } else {
+                observerOn.handler.post(() -> {
+                    observerOn.observer.onChange();
+                });
+            }
+        }
+    }
+}

+ 67 - 0
app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java

@@ -0,0 +1,67 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Daniel Calviño Sánchez
+ * Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.call;
+
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+
+/**
+ * Mutable data model for (remote) call participants.
+ *
+ * There is no synchronization when setting the values; if needed, it should be handled by the clients of the model.
+ */
+public class MutableCallParticipantModel extends CallParticipantModel {
+
+    public MutableCallParticipantModel(String sessionId) {
+        super(sessionId);
+    }
+
+    public void setUserId(String userId) {
+        this.userId.setValue(userId);
+    }
+
+    public void setNick(String nick) {
+        this.nick.setValue(nick);
+    }
+
+    public void setIceConnectionState(PeerConnection.IceConnectionState iceConnectionState) {
+        this.iceConnectionState.setValue(iceConnectionState);
+    }
+
+    public void setMediaStream(MediaStream mediaStream) {
+        this.mediaStream.setValue(mediaStream);
+    }
+
+    public void setAudioAvailable(Boolean audioAvailable) {
+        this.audioAvailable.setValue(audioAvailable);
+    }
+
+    public void setVideoAvailable(Boolean videoAvailable) {
+        this.videoAvailable.setValue(videoAvailable);
+    }
+
+    public void setScreenIceConnectionState(PeerConnection.IceConnectionState screenIceConnectionState) {
+        this.screenIceConnectionState.setValue(screenIceConnectionState);
+    }
+
+    public void setScreenMediaStream(MediaStream screenMediaStream) {
+        this.screenMediaStream.setValue(screenMediaStream);
+    }
+}