Demo App

An app demonstrating consumption of Huddle01 Android SDK

Initialization

public class Application extends android.app.Application {
    @Override
    public void onCreate() {
        super.onCreate();
        HuddleClient.initApp(this);
    }
}

UI

Java

public class MeView extends RelativeLayout {

    public MeView(@NonNull Context context) {
        super(context);
        init(context);
    }

    public MeView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MeView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MeView(
            @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    com.rohg007.android.huddle01_android_sdk.databinding.ViewMeBindingImpl mBinding;

    private void init(Context context) {
        mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.view_me, this, true);
        mBinding.peerView.videoRenderer.init(PeerConnectionUtils.getEglContext(), null);
    }

    public void setProps(MeProps props, final HuddleClient huddleClient) {

        // set view model.
        mBinding.peerView.setPeerViewProps(props);

        // register click listener.
        mBinding.peerView.info.setOnClickListener(
                view -> {
                    Boolean showInfo = props.getShowInfo().get();
                    props.getShowInfo().set(showInfo != null && showInfo ? Boolean.FALSE : Boolean.TRUE);
                });

        mBinding.peerView.meDisplayName.setOnEditorActionListener(
                (textView, actionId, keyEvent) -> {
                    if (actionId == EditorInfo.IME_ACTION_DONE) {
                        huddleClient.changeDisplayName(textView.getText().toString().trim());
                        return true;
                    }
                    return false;
                });

        mBinding.peerView.videoRenderer.setZOrderMediaOverlay(true);

        // set view model.
        mBinding.setMeProps(props);

        // register click listener.
        mBinding.mic.setOnClickListener(
                view -> {
                    if (MeProps.DeviceState.ON.equals(props.getMicState().get())) {
                        huddleClient.muteMic();
                    } else {
                        huddleClient.unmuteMic();
                    }
                });
        mBinding.cam.setOnClickListener(
                view -> {
                    if (MeProps.DeviceState.ON.equals(props.getCamState().get())) {
                        huddleClient.disableCam();
                    } else {
                        huddleClient.enableCam();
                    }
                });
        mBinding.changeCam.setOnClickListener(view -> huddleClient.changeCam());
    }
}

XML

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/onboarding_"
        android:orientation="vertical"
        tools:context=".RoomActivity">

        <EditText
            android:id="@+id/chat_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginLeft="10dp"
            android:layout_marginBottom="10dp"
            android:imeOptions="actionDone"
            android:singleLine="true" />

        <ImageView
            android:id="@+id/room_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="5dp"
            android:padding="5dp"
            android:src="@drawable/ic_state_new_close"
            bind:edias_state="@{roomProps.connectionState}"
            bind:edias_state_animation="@{roomProps.getConnectingAnimation}" />

        <TextView
            android:id="@+id/version"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_marginTop="5dp"
            android:layout_marginEnd="10dp"
            android:padding="5dp"
            android:textColor="@color/text_color" />


        <TextView
            android:id="@+id/invitation_link"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:background="@drawable/bg_area"
            android:paddingLeft="15dp"
            android:paddingTop="8dp"
            android:paddingRight="15dp"
            android:paddingBottom="8dp"
            android:text="@string/invitation_link"
            android:textColor="@color/text_color"
            android:textSize="15sp"
            bind:edias_link="@{roomProps.invitationLink}" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/remote_peers"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />

        <com.leinardi.android.speeddial.SpeedDialView
            android:id="@+id/reaction_fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/chat_input"
            android:layout_alignParentEnd="true"
            bind:sdMainFabClosedSrc="@drawable/ic_baseline_tag_faces_24" />

        <com.rohg007.android.huddle01_android_sdk.views.MeView
            android:id="@+id/me"
            android:layout_width="220dp"
            android:layout_height="200dp"
            android:layout_alignParentBottom="true"
            android:layout_marginLeft="10dp"
            android:layout_marginBottom="60dp" />

    </RelativeLayout>

    <data>

        <variable
            name="roomProps"
            type="com.rohg007.android.huddle01_android_sdk.viewmodels.RoomProps" />
    </data>

</layout>

Room

public class RoomActivity extends AppCompatActivity {

    private static final String TAG = RoomActivity.class.getSimpleName();
    private static final int REQUEST_CODE_SETTING = 1;

    private String mRoomId, mPeerId, mDisplayName;

    private ActivityRoomBinding activityRoomBinding;
    private HuddleClient huddleClient;
    private StateStore stateStore;
    private String apiKey = API_KEY_HERE;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        activityRoomBinding = DataBindingUtil.setContentView(this, R.layout.activity_room);
        stateStore = new StateStore();
        createRoom();
        checkPermission();
        setupReactions();
    }

    @SuppressLint("NonConstantResourceId")
    private void setupReactions(){
        activityRoomBinding.reactionFab.inflate(R.menu.reactions_menu);
        activityRoomBinding.reactionFab.setOnActionSelectedListener(actionItem -> {
            switch (actionItem.getId()){
                case R.id.tears_of_joy: {
                    huddleClient.sendReaction(Reactions.TEARS_OF_JOY);
                    activityRoomBinding.reactionFab.close();
                    return true;
                }
                case R.id.crying_face: {
                    huddleClient.sendReaction(Reactions.CRYING_FACE);
                    activityRoomBinding.reactionFab.close();
                    return true;
                }
                case R.id.hundred: {
                    huddleClient.sendReaction(Reactions.HUNDRED);
                    activityRoomBinding.reactionFab.close();
                    return true;
                }
                case R.id.thumbs_up: {
                    huddleClient.sendReaction(Reactions.THUMBS_UP);
                    activityRoomBinding.reactionFab.close();
                    return true;
                }
                case R.id.thumbs_down: {
                    huddleClient.sendReaction(Reactions.THUMBS_DOWN);
                    activityRoomBinding.reactionFab.close();
                    return true;
                }
                case R.id.rocket: {
                    huddleClient.sendReaction(Reactions.ROCKET);
                    activityRoomBinding.reactionFab.close();
                    return true;
                }
                default: {
                    Toast.makeText(this, "invalid reaction", Toast.LENGTH_SHORT).show();
                    activityRoomBinding.reactionFab.close();
                    return false;
                }
            }
        });
    }

    private void createRoom(){
        loadRoomConfig();
        activityRoomBinding.invitationLink.setText("RoomId: "+mRoomId);
        huddleClient = new HuddleClient.Builder(getApplicationContext(), apiKey)
                .setPeerId(mPeerId)
                .setRoomId(mRoomId)
                .setDisplayName(mDisplayName)
                .setCanConsume(true)
                .setCanProduce(true)
                .setCanUseDataChannel(true)
                .setRoomEventListener(listener)
                .setMeListener(meListener)
                .setFrontCamEnabledOnInit(true)
                .build();

        getViewModelStore().clear();
        initViewModel();
    }

    void joinRoom(){
        huddleClient.joinRoom();
    }

    private void loadRoomConfig(){
        Intent intent = getIntent();
        String roomId = intent.getStringExtra("roomId");
        if(roomId!=null){
            mRoomId = roomId;
        } else {
            mRoomId = Utils.getRandomString(8);
        }
        mPeerId = Utils.getRandomString(8);
        mDisplayName = Utils.getRandomString(8);
    }


    private void initViewModel(){
        EdiasProps.Factory factory = new EdiasProps.Factory(getApplication(), stateStore);
        RoomProps roomProps = new ViewModelProvider(this, factory).get(RoomProps.class);
        roomProps.connect(this);
        activityRoomBinding.invitationLink.setOnClickListener(v -> {
            String invitationLink = roomProps.getInvitationLink().get();
            ClipboardCopy.clipboardCopy(getApplication(), invitationLink, R.string.invite_link_copied);
        });
        activityRoomBinding.setRoomProps(roomProps);

        MeProps meProps = new ViewModelProvider(this, factory).get(MeProps.class);
        meProps.connect(this);
        activityRoomBinding.me.setProps(meProps, huddleClient);

        activityRoomBinding.chatInput.setOnEditorActionListener((v, actionId, event) -> {
            if(actionId == EditorInfo.IME_ACTION_DONE){
                huddleClient.sendChatMessage(v.getText().toString());
                v.setText("");
                return true;
            } else
                return false;
        });

        PeerAdapter peerAdapter = new PeerAdapter(stateStore, this);
        activityRoomBinding.remotePeers.setLayoutManager(new LinearLayoutManager(this));
        activityRoomBinding.remotePeers.setAdapter(peerAdapter);

        stateStore.getPeers().observe(this, peers -> {
            List<Peer> peerList = peers.getAllPeers();
            if(peerList.isEmpty()){
                activityRoomBinding.remotePeers.setVisibility(View.GONE);
                activityRoomBinding.roomState.setVisibility(View.VISIBLE);
            } else {
                activityRoomBinding.remotePeers.setVisibility(View.VISIBLE);
                activityRoomBinding.roomState.setVisibility(View.GONE);
            }
            peerAdapter.replacePeers(peerList);
        });

        stateStore.getNotify().observe(this, notify -> {
            SpannableStringBuilder text = SpannableStringBuilder.valueOf(notify.getText());
            if(notify.getType().equals("error"))
                text.setSpan(new ForegroundColorSpan(Color.RED), 0, text.length(), 0);
            Toast.makeText(this, text, notify.getTimeout()).show();
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(requestCode == REQUEST_CODE_SETTING){
            destroyRoom();
            createRoom();
            joinRoom();
        } else super.onActivityResult(requestCode, resultCode, data);
    }

    private void destroyRoom(){
        huddleClient.closeRoom();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        destroyRoom();
    }

    private void checkPermission() {
        String[] permissions = {
                Manifest.permission.INTERNET,
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.CAMERA,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };
        String rationale = "Please provide permissions";
        Permissions.Options options =
                new Permissions.Options().setRationaleDialogTitle("Info").setSettingsDialogTitle("Warning");
        Permissions.check(this, permissions, rationale, options, permissionHandler);
    }

    private final PermissionHandler permissionHandler =
            new PermissionHandler() {
                @Override
                public void onGranted() {
                    joinRoom();
                }
            };

    private final RoomEventListener listener = new RoomEventListener() {

        @Override
        public void onRoomUrlGenerated(String roomId, String roomUrl) {
            stateStore.setRoomUrl(roomId, roomUrl);
        }

        @Override
        public void onRoomStateChanged(ConnectionState state) {
            stateStore.setRoomState(state);
        }

        @Override
        public void onError(String message) {

        }

        @Override
        public void onPeerChanged(String actionType, String peerId, @Nullable JSONObject info, @Nullable String displayName) {
            switch (actionType){
                case Constants.ACTION_ADDED: stateStore.addPeer(peerId, info);
                break;
                case Constants.ACTION_DISPLAY_NAME_CHANGED: stateStore.setPeerDisplayName(peerId, displayName);
                break;
                case Constants.ACTION_REMOVED: stateStore.removePeer(peerId);
                break;
                default: Toast.makeText(getApplicationContext(), "Invalid Action Type", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onProducersChanged(String type, HuddleProducer producer) {
            switch (type){
                case Constants
                        .ACTION_ADDED: stateStore.addProducer(producer);
                break;
                case Constants.ACTION_PAUSED: stateStore.setProducerPaused(producer.getId());
                break;
                case Constants.ACTION_RESUMED: stateStore.setProducerResumed(producer.getId());
                break;
                case Constants.ACTION_REMOVED: stateStore.removeProducer(producer.getId());
                break;
                default: Toast.makeText(getApplicationContext(), "Invalid Producer Action Type", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onConsumerAdded(String peerId, String consumerType, HuddleConsumer consumer, boolean remotelyPaused) {
            stateStore.addConsumer(peerId, consumerType, consumer, remotelyPaused);
        }

        @Override
        public void onConsumerRemoved(String peerId, String consumerId) {
            stateStore.removeConsumer(peerId, consumerId);
        }

        @Override
        public void onConsumerStateChanged(String actionType, String consumerId, String originator) {
            switch (actionType){
                case Constants.ACTION_PAUSED: stateStore.setConsumerPaused(consumerId, originator);
                break;
                case Constants.ACTION_RESUMED: stateStore.setConsumerResumed(consumerId, originator);
                break;
                default: Toast.makeText(getApplicationContext(), "Invalid Action Type", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onDataProducerChanged(String actionType, HuddleDataProducer dataProducer) {
            switch (actionType){
                case Constants.ACTION_ADDED: stateStore.addDataProducer(dataProducer);
                break;
                case Constants.ACTION_REMOVED: stateStore.removeDataProducer(dataProducer.getId());
                break;
                default: Toast.makeText(getApplicationContext(), "Invalid Action Type", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onDataConsumerChanged(String actionType, String peedId, HuddleDataConsumer dataConsumer) {
            switch (actionType) {
                case Constants.ACTION_ADDED: stateStore.addDataConsumer(peedId, dataConsumer);
                break;
                case Constants.ACTION_REMOVED: stateStore.removeDataConsumer(peedId, dataConsumer.getId());
                break;
                default: Toast.makeText(getApplicationContext(), "Invalid Action Type", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onReactionReceived(Peer peer, String reaction) {
            Toast.makeText(getApplicationContext(), peer.getDisplayName()+" reacted: "+reaction, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onChatReceived(Peer peer, String message) {
            Toast.makeText(getApplicationContext(), peer.getDisplayName()+" messaged: "+message, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNotification(Notify notify) {
            Toast.makeText(getApplicationContext(), notify.getText(), Toast.LENGTH_SHORT).show();
        }
    };

    private final MeListener meListener = new MeListener() {
        @Override
        public void onMeReceived(String peerId, String displayName, DeviceInfo deviceInfo) {
            stateStore.setMe(peerId, displayName, deviceInfo);
        }

        @Override
        public void onMediaCapabilitiesReceived(boolean canSendMic, boolean canSendCam) {
            stateStore.setMediaCapabilities(canSendMic, canSendCam);
        }

        @Override
        public void onNewDisplayName(String displayName) {
            stateStore.setDisplayName(displayName);
        }
    };
}

State

public class StateStore {

    CustomTypeMutableLiveData<RoomInfo> roomInfo = new CustomTypeMutableLiveData<>(RoomInfo::new);
    CustomTypeMutableLiveData<Me> me = new CustomTypeMutableLiveData<>(Me::new);
    CustomTypeMutableLiveData<Producers> producers = new CustomTypeMutableLiveData<>(Producers::new);
    CustomTypeMutableLiveData<DataProducers> dataProducers = new CustomTypeMutableLiveData<>(DataProducers::new);
    CustomTypeMutableLiveData<Peers> peers = new CustomTypeMutableLiveData<>(Peers::new);
    CustomTypeMutableLiveData<Consumers> consumers = new CustomTypeMutableLiveData<>(Consumers::new);
    CustomTypeMutableLiveData<DataConsumers> dataConsumers = new CustomTypeMutableLiveData<>(DataConsumers::new);
    MutableLiveData<Notify> notify = new MutableLiveData<>();

    public void setRoomUrl(String roomId, String url){
        roomInfo.postValue(roomInfo->{
            roomInfo.setRoomId(roomId);
            roomInfo.setUrl(url);
        });
    }

    public void setRoomState(ConnectionState state){
        roomInfo.postValue(roomInfo->{
            roomInfo.setConnectionState(state);
        });
        if(state == ConnectionState.CLOSED){
            peers.postValue(Peers::clear);
            me.postValue(Me::clear);
            producers.postValue(Producers::clear);
            consumers.postValue(Consumers::clear);
        }
    }

    public void setMe(String peerId, String displayName, DeviceInfo device){
        me.postValue(me->{
            me.setId(peerId);
            me.setDisplayName(displayName);
            me.setDeviceInfo(device);
        });
    }

    public void setMediaCapabilities(boolean canSendMic, boolean canSendCam){
        me.postValue(me-> {
            me.setCanSendMic(canSendMic);
            me.setCanSendCam(canSendCam);
        });
    }

    public void setDisplayName(String displayName){
        me.postValue(me->me.setDisplayName(displayName));
    }

    public void addProducer(HuddleProducer producer){
        producers.postValue(producers->producers.addProducer(producer));
    }

    public void setProducerPaused(String producerId){
        producers.postValue(producers->producers.setProducerPaused(producerId));
    }

    public void setProducerResumed(String producerId){
        producers.postValue(producers->producers.setProducerResumed(producerId));
    }

    public void removeProducer(String producerId){
        producers.postValue(producers->producers.removeProducer(producerId));
    }

    public void addDataProducer(HuddleDataProducer dataProducer){
        dataProducers.postValue(dataProducers->dataProducers.addDataProducer(dataProducer));
    }

    public void removeDataProducer(String dataProducerId){
        dataProducers.postValue(dataProducers->dataProducers.removeDataProducer(dataProducerId));
    }

    public void addPeer(String peerId, JSONObject info){
        peers.postValue(peers-> {
            peers.addPeer(peerId, info);
        });
    }

    public void setPeerDisplayName(String peerId, String displayName){
        peers.postValue(peers->peers.setPeerDisplayName(peerId, displayName));
    }

    public void removePeer(String peerId){
        roomInfo.postValue(roomInfo->{
            if(!TextUtils.isEmpty(peerId) && roomInfo.getActiveSpeakerId().equals(peerId))
                roomInfo.setActiveSpeakerId(null);
            if(!TextUtils.isEmpty(peerId) && roomInfo.getStatsPeerId().equals(peerId))
                roomInfo.setStatsPeerId(null);
        });
        peers.postValue(peers->peers.removePeer(peerId));
    }

    public void addConsumer(String peerId, String type, HuddleConsumer consumer, boolean remotelyPaused){
        consumers.postValue(consumers->consumers.addConsumer(type, consumer, remotelyPaused));
        peers.postValue(peers->peers.addConsumer(peerId, consumer));
    }

    public void removeConsumer(String peerId, String consumerId){
        consumers.postValue(consumers->consumers.removeConsumer(consumerId));
        peers.postValue(peers->peers.removeConsumer(peerId, consumerId));
    }

    public void setConsumerPaused(String consumerId, String originator){
        consumers.postValue(consumers->consumers.setConsumerPaused(consumerId, originator));
    }

    public void setConsumerResumed(String consumerId, String originator){
        consumers.postValue(consumers->consumers.setConsumerResumed(consumerId, originator));
    }

    public void addDataConsumer(String peerId, HuddleDataConsumer dataConsumer){
        dataConsumers.postValue(dataConsumers->dataConsumers.addDataConsumer(dataConsumer));
        peers.postValue(peers->peers.addDataConsumer(peerId, dataConsumer));
    }

    public void removeDataConsumer(String peerId, String dataConsumerId){
        dataConsumers.postValue(dataConsumers->dataConsumers.removeDataConsumer(dataConsumerId));
        peers.postValue(peers->peers.removeDataConsumer(peerId, dataConsumerId));
    }

    public CustomTypeMutableLiveData<RoomInfo> getRoomInfo() {
        return roomInfo;
    }

    public CustomTypeMutableLiveData<Me> getMe() {
        return me;
    }

    public CustomTypeMutableLiveData<Producers> getProducers() {
        return producers;
    }

    public CustomTypeMutableLiveData<Peers> getPeers() {
        return peers;
    }

    public CustomTypeMutableLiveData<Consumers> getConsumers() {
        return consumers;
    }

    public MutableLiveData<Notify> getNotify() {
        return notify;
    }

For any help, reach out to us on Slack. We are available 24*7 at: Huddle01 Community.

Last updated