As soon as the 2nd user connects to the channel 1st user is disconnected

public void JoinChannel(string channelName, ChannelType channelType, bool connectAudio, bool connectText, bool transmissionSwitch = true, Channel3DProperties properties = null)
{
    if (LoginSession.State == LoginState.LoggedIn)
    {
        Channel channel = new Channel(channelName, channelType, properties);

        IChannelSession channelSession = LoginSession.GetChannelSession(channel);

        channelSession.BeginConnect(connectAudio, connectText, transmissionSwitch, channelSession.GetConnectToken(), ar =>
        {
            try
            {
                channelSession.EndConnect(ar);
            }
            catch(Exception e)
            {
                Debug.LogError($"Could not connect to channel: {e.Message}");
                return;
            }
        });
    } else
    {
        Debug.LogError("Can't join a channel when not logged in.");
    }
}

JoinChannel sample code in the doc is basically creating a new channel, how does other devices join that same channel? What I have understood so far is to use the Channel Token Key to connect to the channel, but how do we do that, I am unclear which API to use to do so.

Should we use channelSession.GetConnectToken() token key data on the other device to connect to the same, if so which API call do we do.

What I am trying to achieve, with what I have currently understood.

I have 2 copies of the project.
ProjA and ProjB, I run the unity editor of ProjA , login to vivox and then create the channel using the BeginConnect API as shown in the sample above.
I run the unity editor of ProjB, login to vivox and then join the same channel created by unity editor ProjA, what I have understood is I can use the TokenKey generated by ProjA to connect to the same channel in ProjB.

Is my understanding of the architecture correct?

Could I be directed to the right doc so that I can achieve the above or explaining the API call would also do. Thank you

@jeeth_unity
The reason you are able to join the same channel as project B is because you have the same channel name. Every Token Key generated from GetChannelSession by every client (Project A and Project B are 2 different clients) should be the same as long as the channel name is the same! Your understanding of the architecture is correct!!! But the answer to your previous question is you need a… [separate server/source of truth/database] for Vivox and your Client Applications to know all the existing, available channels to join under the same credentials available in the Vivox/Unity!!! Every Client using Vivox(meaning you are connected to Vivox Severs) will not what channels are available unless they know the channels by name. You can implement a server using Unity Net Code, Mirror Networking, Photon/ Photon Pun 2, or Asp.Net Core API, or implement your own solution depending on your skill set. The basis is that there needs to be a server that knows all available channels to join, at all times, and even if you don’t have a multiplayer game, there will always be an available list of Connected/Created channels, whether these are pre-determined by developers or users. If you need more help or explanation you can look at my basic YouTube videos,

or check my Discord channel where I can help you better(you can upload files and I can give code reviews or code snippets to help you out)

1 Like

@MurphyMurph_21 I can connect to the same channel by name? So if ProjA creates channel “Bastion” ProjB can join the same channel “Bastion” by just using the same name, not the token key?

We already have a backend that can know the channels for the clients to connect to.
This is a code snippet before calling BeginConnect, I am in the assumption that tokenKey needs to be shared with other clients to connect to the same channel

 Channel channel = new Channel(aChannelName, aChannelType, aProperties);
 IChannelSession channelSession = m_LoginSession.GetChannelSession(channel);
 // Subscribe to property changes for all channels.
 channelSession.PropertyChanged += OnChannelPropertyChanged;
 string tokenKey = channelSession.GetConnectToken();

I have tried calling the same JoinChannel on both editors, I have noticed the If ProjA initially creates the channel using the above API sample immediately after ProjB connect to the same channel by using the channel name

ProjA editors console shows
Audio disconnected in Bastion
Text disconnected in Bastion
Channel disconnected Bastion

So basically the 1st user disconnects from the channel when 2nd user connects to the same channel.
These logs are printed in
channelSession.PropertyChanged += OnChannelPropertyChanged

My objective here is ProjA unity editor and ProjB unity editor should connect to the same channel, and Text chat and VOIP should be working. User ProjA and User ProjB should be able to communicate, how can I achieve that? Please help.

Thanks to @MurphyMurph_21 and his discord members I was able to sort this issue out instead of using ChannelSession.GetConnectToken() API I used ChannelSession.GetConnectToken(“TokenKey”, Timespan) where TokenKey is the key generated by the unity game service dashboard and Timespan is how long the token is kept valid.

string tokenKey = channelSession.GetConnectToken("Token_From_UGS",TimeSpan.FromSeconds(90));

Use tokenKey when calling ChannelSession.BeginConnect API
The token will be valid for 1:30 min

2 Likes

Sadly the same behaviour has started again, has anybody faced a similar problem. I don’t get any errors. Just that the 1st user logs Disconnectd from Audio, Text and Channel which is called by the propertychange event

channelSession.PropertyChanged += OnChannelPropertyChanged;
```csharp
*private void OnChannelPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
var channelSession = (IChannelSession)sender;

        Debug.Log(
            $"[VivoxLogin][OnChannelPropertyChanged] Property change: {propertyChangedEventArgs.PropertyName}");
        // This example only checks for AudioState changes.
        if (propertyChangedEventArgs.PropertyName == "AudioState")
        {
            switch (channelSession.AudioState)
            {
                case ConnectionState.Connecting:
                    Debug.Log("Audio connecting in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Connected:
                    Debug.Log("Audio connected in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Disconnecting:
                    Debug.Log("Audio disconnecting in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Disconnected:
                    Debug.Log("Audio disconnected in " + channelSession.Key.Name);
                    break;
            }
        }
        else  if (propertyChangedEventArgs.PropertyName == "TextState")
        {
            switch (channelSession.TextState)
            {
                case ConnectionState.Connecting:
                    Debug.Log("Text connecting in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Connected:
                    Debug.Log("Text connected in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Disconnecting:
                    Debug.Log("Text disconnecting in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Disconnected:
                    Debug.Log("Text disconnected in " + channelSession.Key.Name);
                    break;
            }
        }
        else if (propertyChangedEventArgs.PropertyName == "ChannelState")
        {
            switch (channelSession.ChannelState)
            {
                case ConnectionState.Connecting:
                    Debug.Log("Channel connecting in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Connected:
                    Debug.Log("Channel connected in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Disconnecting:
                    Debug.Log("Channel disconnecting in " + channelSession.Key.Name);
                    break;

                case ConnectionState.Disconnected:
                    Debug.Log("Channel disconnected in " + channelSession.Key.Name);
                    break;
            }
        }
    }*

```

Hi Jeeth,

There are a couple clarifications I’d like to make.

The value returned by channelSession.GetConnectToken() is what we call a Vivox Access Token (please see this documentation). These VATs can only be used once and will expire even if not used. They cannot be shared between clients. Each client needs a uniquely generated VAT, which we recommend to be generated by the game server and sent to the client when needed. For example, client1 asks the game server what channel to join. The game server replies to join channel1 and also provides the VAT to do so. For security, live clients should not have your VAT generation key.

Any client can make a channel, but as mentioned above, they need a VAT to do so. Your method of channel creation seems correct.

If the second player is kicking the first player when joining, it sounds like you might be using the same username. Usernames must be unique and should be assigned by your game client or game server (please see this documentation). If the same username joins a channel, the Vivox backend assumes that there must have been some kind of disconnection and the original user has rejoined. To prevent issues of duplicate disconnected users, it will kick the older username.

Please let me know if you have any further issues.

2 Likes

Thanks to @MurphyMurph_21 I was able to narrow down the problem where it broke. According to the QUICK GUIDE: https://docs.vivox.com/v5/general/unity/15_1_170000/en-us/Default.htm#Unity/vivox-unity-first-steps.htm?TocPath=Vivox%20Unity%20SDK%20documentation|_____1
In the login method, the account instance creation is done this way

var account = new Account(displayName);

but this is where it failed for me as soon as I switched to @MurphyMurph_21 implementation of injecting issuer and domain into instance creation it started to work. Using the base class AccountId instead.

new AccountId(“issuer value here (DASHBOARD)”, aDisplayName, “domain value here (DASHBOARD)”);
AFTER THIS CHANGE, IT HAS BEEN WORKING 100% of the time.