Android SDK v3 migration guide
Voximplant Android SDK v3 has introduced major breaking changes to provide more functionality and improve the SDK scalability. This guide explains how to smoothly migrate the application code base to use the new API.
Contents
SDK Initialization
Core module initialization
The Core module should be initialized first via VICore.initialize. It is recommended to perform this initialization at application startup, though it can be done at any time prior to using the SDK.
In SDK v3, the executor used for receiving SDK events is created automatically as a single-thread executor. It can be manually configured via the VICore.callbackExecutor property after initialization.
From v2:
val client = Voximplant.getClientInstance(
/* executor = */ Executors.newSingleThreadExecutor(),
/* context = */ applicationContext,
/* clientConfig = */ ClientConfig(),
)
To v3:
// Optional: Configure a custom callback executor
VICore.callbackExecutor = Executors.newSingleThreadExecutor()
VICore.initialize(applicationContext)
Initialization state can be verified using VICore.isInitialized.
Calls module initialization
The Calls module requires the Core module to be initialized first using the VICalls.initialize, but its initialization does not need to occur immediately following VICore.initialize.
VICalls.initialize()
Initialization state can be verified using VICalls.isInitialized.
Connect and authenticate the Voximplant users
The Client API is now the object and can be accessed from anywhere. The API to connect and log in the users are redesigned and provided with the callbacks with the extended error descriptions.
To establish the connection to the Voximplant cloud it is required to specify the node the Voximplant account belongs to via the ConnectOptions.
From v2:
import com.voximplant.sdk.client.AuthParams
import com.voximplant.sdk.client.IClient
import com.voximplant.sdk.client.IClientLoginListener
import com.voximplant.sdk.client.IClientSessionListener
import com.voximplant.sdk.client.Node
class SomeClass : IClientSessionListener, IClientLoginListener {
private lateinit var client: IClient
// ctor and other logic here
// Note: you also need to implement other methods from
// IClientLoginListener and IClientSessionListener
override fun onConnectionEstablished() {
Log.i(TAG, "Connection established")
client.login("myuser@myapp.myaccount.voximplant.com", "mypass")
}
override fun onLoginSuccessful(
displayName: String,
authParams: AuthParams?,
) {
Log.i(TAG, "Login succeeded")
}
fun connectToVoximplant() {
client.setClientSessionListener(this)
client.setClientLoginListener(this)
client.connect(Node)
}
}
To v3:
import com.voximplant.android.sdk.core.AuthParams
import com.voximplant.android.sdk.core.Client
import com.voximplant.android.sdk.core.ClientSessionListener
import com.voximplant.android.sdk.core.ConnectOptions
import com.voximplant.android.sdk.core.ConnectionCallback
import com.voximplant.android.sdk.core.ConnectionError
import com.voximplant.android.sdk.core.DisconnectReason
import com.voximplant.android.sdk.core.LoginCallback
import com.voximplant.android.sdk.core.LoginError
import com.voximplant.android.sdk.core.Node
class SomeClass {
val clientSessionListener = object : ClientSessionListener {
override fun onConnectionClosed(reason: DisconnectReason) {
Log.w(TAG, "Connection closed")
Client.setClientSessionListener(null)
}
}
fun connect() {
Client.connect(
options = ConnectOptions(node = Node), // Select Node from Voximplant Cloud
callback = object : ConnectionCallback {
override fun onFailure(error: ConnectionError) {
Log.e(TAG, "Connection failed – $error")
}
override fun onSuccess() {
Log.i(TAG, "Connection succeeded")
Client.setClientSessionListener(clientSessionListener)
// You can now log in
}
},
)
}
fun login() {
Client.login(
username = "myuser@myapp.myaccount.voximplant.com",
password = "mypass",
callback = object : LoginCallback {
override fun onFailure(error: LoginError) {
Log.e(TAG, "Login failed – $error")
}
override fun onSuccess(displayName: String, authParams: AuthParams?) {
Log.i(TAG, "Login succeeded")
}
},
)
}
}
Local video stream changes
Voximplant Android SDK v3 allows developers to create a local video stream outside an active call or conference. It simplifies the implementation of such functionality as video preview and camera configuration. A local video stream should be created based on a video source.
Each video support provides API for its configuration. The SDK provides support for the following video sources:
CameraVideoSource — allows developers to setup and manage cameras on the Android device
CustomVideoSource — allows developers to inject video frames created outside the SDK (on the application side) to a call or a conference
ScreenCaptureVideoSource — allows developers to capture the Android device screen
A video source starts producing frames (starts capturing) only if at least one the following conditions is met:
- a video renderer is added to the local video stream
- a local video stream is attached to an active call or a conference
If a video source requires a system permission, for example a camera permission, it is recommended to request the permission before creating a local video stream.
A video source stops producing frames (stops capturing) if both conditions are met:
- a local video stream is not rendered to any renderer
- a local video stream is not sent in any call or conference
If a video call or a conference has ended, but a local video stream still has any renderers, video source does not release its resources. It is important to carefully manage local video streams on the application side to avoid unexpected system indicators for a camera or screen capture.
Create a local video stream based on the camera video source:
val cameraVideoSource = CameraVideoSource
val localVideoStream = LocalVideoStream(cameraVideoSource)
Calls
Voximplant Android SDK v3 has a separate classes that represent a call and a conference. It provides a clearer visibility of the SDK functionality for calls and conferences.
Notable changes:
Separated call and conference settings to start a call or a conference
Separated call and conferences events
Endpoints are now available only for the conferences. Information about another call participant as well as its events is now available via the
CallAPI.Local video stream management before a call/conference — it is now possible to create and render a local video stream before a call or a conference starts. New API provide more flexible way to manage the video stream sources such as camera, custom, or screen sharing video source.
Native webrtc libraries are loaded on the calls module initialization
Before you start using the calls module API, you should initialize the module via the VICalls.initialize API.
Start an outgoing audio call
The VideoFlags API that represents the video settings for a call is replaced with CallSettings.receiveVideo and CallSettings.localVideoStream properties. Video is disabled by default.
From v2:
import com.voximplant.sdk.call.CallException
import com.voximplant.sdk.call.CallSettings
import com.voximplant.sdk.call.ICall
import com.voximplant.sdk.call.VideoFlags
// 1. Prepare callSettings
val callSettings = CallSettings()
callSettings.videoFlags = VideoFlags(false, false)
// 2. Get an ICall instance
val call: ICall? = client.call("Number to call", callSettings)
// 3. Start the call
try {
call!!.start()
} catch (callException: CallException) {
// handle error
}
To v3:
import com.voximplant.android.sdk.calls.Call
import com.voximplant.android.sdk.calls.CallException
import com.voximplant.android.sdk.calls.CallSettings
import com.voximplant.android.sdk.calls.VICalls
// 1. Create a Call instance
val call: Call? = VICalls.createCall(
number = "number to call",
callSettings = CallSettings(),
)
// 2. Start the call
try {
call!!.start()
} catch (callException: CallException) {
// handle error
}
Start an outgoing video call
To start an outgoing video call, it is required to create a local video stream first.
Call cannot be created with a local video stream with screen capture video source. However, it is possible to start sending the screen sharing after the call is connected.
From v2:
import com.voximplant.sdk.call.CallException
import com.voximplant.sdk.call.CallSettings
import com.voximplant.sdk.call.ICall
import com.voximplant.sdk.call.VideoFlags
// 1. Prepare callSettings
val callSettings = CallSettings()
callSettings.videoFlags = VideoFlags(true, true)
// 2. Get an ICall instance
val call: ICall? = client.call(user, callSettings)
// 3. Start the call
try {
call!!.start()
} catch (callException: CallException) {
// handle error
}
To v3:
import com.voximplant.android.sdk.calls.Call
import com.voximplant.android.sdk.calls.CallException
import com.voximplant.android.sdk.calls.CallSettings
import com.voximplant.android.sdk.calls.LocalVideoStream
import com.voximplant.android.sdk.calls.VICalls
import com.voximplant.android.sdk.calls.video.CameraVideoSource
// 1. Create a local video stream
val cameraVideoSource = CameraVideoSource
val localVideoStream = LocalVideoStream(cameraVideoSource)
// 2. Prepare the call settings
val callSettings = CallSettings()
callSettings.localVideoStream = localVideoStream
callSettings.receiveVideo = true
// 3. Create a call
val call: Call? = VICalls.createCall(
number = "number_to_call",
callSettings = callSettings,
)
// 4. Start the call
try {
call!!.start()
} catch (callException: CallException) {
// handle error
}
Handle incoming calls
To receive the incoming calls it is required to subscribe to IncomingCallListener via the VICalls.setIncomingCallListener API.
From v2:
import com.voximplant.android.sdk.calls.CallException
import com.voximplant.android.sdk.calls.CallSettings
import com.voximplant.sdk.client.IClient
import com.voximplant.sdk.client.IClientIncomingCallListener
class SomeClass : IClientIncomingCallListener {
private lateinit var client: IClient
fun subscribeForIncomingCalls() {
client.setClientLoginListener(this)
}
override fun onIncomingCall(
call: ICall,
video: Boolean,
headers: Map<String?, String?>?
) {
try {
call.answer(CallSettings())
} catch (callException: CallException) {
// handle error
}
}
}
To v3:
import com.voximplant.android.sdk.calls.Call
import com.voximplant.android.sdk.calls.CallException
import com.voximplant.android.sdk.calls.CallSettings
import com.voximplant.android.sdk.calls.IncomingCallListener
class SomeClass {
val incomingCallListener = object : IncomingCallListener {
override fun onIncomingCall(
call: Call,
hasIncomingVideo: Boolean,
headers: Map<String, String>?,
) {
try {
call.answer(CallSettings())
} catch (callException: CallException) {
// handle error
}
}
}
init {
VICalls.setIncomingCallListener(incomingCallListener)
}
}
Handle call events changes
Call’s and conference’s events are separated into different interface listeners: CallListener and ConferenceListener.
The following call events are renamed:
ICalListener.onCallRinging → CallListener.onStartRinging — the event is triggered when the Call.ring method is called on the scenario side
ICallListener.onCallAudioStarted → CallListener.onStopRinging — the event is triggered when the call media starts soon and the progress tones played on the application side should be stopped.
Remote participant in a call is no more represented via the Endpoint interface. To handle the events related to a remote video stream in a call, it is required to subscribe to the following events:
CallListener.onRemoteVideoStreamAdded — the event is triggered when a remote participant has started to send a video in a call
CallListener.onRemoteVideoStreamRemoved — the event is triggered when a remote participant has stopped sending a video in a call
CallListener.onCallDisconnected now has the disconnectReason parameter that specifies the reason the call has ended.
Conferences
Join a conference with video
From v2:
import com.voximplant.sdk.call.CallException
import com.voximplant.sdk.call.CallSettings
import com.voximplant.sdk.call.ICall
import com.voximplant.sdk.call.VideoFlags
// 1. Prepare callSettings
val callSettings = CallSettings()
callSettings.videoFlags = VideoFlags(true, true)
// 2. Get a ICall instance
val call: ICall? = call.callConference(conferenceName, callSettings)
// 3. Start the call
try {
call!!.start()
} catch (callException: CallException) {
// handle error
}
To v3:
import com.voximplant.android.sdk.calls.CallException
import com.voximplant.android.sdk.calls.Conference
import com.voximplant.android.sdk.calls.ConferenceSettings
import com.voximplant.android.sdk.calls.LocalVideoStream
import com.voximplant.android.sdk.calls.VICalls
import com.voximplant.android.sdk.calls.video.CameraVideoSource
// 1. Create a local video stream
val cameraVideoSource = CameraVideoSource
val localVideoStream = LocalVideoStream(cameraVideoSource)
// 2. Prepare the conference settings
val conferenceSettings = ConferenceSettings()
conferenceSettings.localVideoStream = localVideoStream
conferenceSettings.receiveVideo = true
// 3. Create a conference
val conference: Conference? = VICalls.createConference(
number = "destination",
conferenceSettings = conferenceSettings,
)
// 4. Join the conference
try {
conference!!.join()
} catch (callException: CallException) {
// handle error
}
Handle conference events changes
Call’s and conference’s events are separated into different interface listeners: CallListener and ConferenceListener.
New conference events:
ConferenceListener.onLocalVoiceActivityChanged — the event is triggered when a voice activity is detected on the current user in a conference
EndpointListener.onEndpointMuteStateChanged — the event is triggered when a remote conference participant mutes or unmutes his audio via the Conference.muteAudio API
Other changes:
ConferenceListener.onEndpointAdded is not triggered with an endpoint which id is equal to the conference id
ConferenceListener.onConferenceDisconnected now has the
disconnectReasonparameter that specifies the reason the conference has endedConferenceListener.onEndpointRemoved event is moved from EndpointListener to the ConferenceListener
ConferenceListener.onEndpointAdded event is triggered when a conference participant starts the screen sharing
Composable function to render a video stream
Voximplant Android SDK v3 has a new com.voximplant:android-sdk-renderer-compose module that provides a composable function VideoRenderer to render a local or remote video stream in the application built with Jetpack Compose.
import com.voximplant.android.sdk.calls.RenderScaleType
import com.voximplant.android.sdk.renderer.compose.VideoRenderer
@Composable
fun VideoRendererExample() {
VideoRenderer(
modifier = Modifier.wrapContentSize(),
videoStream = videoStream,
renderScaleType = RenderScaleType.Balanced,
)
}
Push notification settings
In Android SDK v2, push notification settings are configured inside ClientConfig when creating the client instance. In Android SDK v3, these settings are decoupled and passed directly to their corresponding API methods.
Mapping Changes:
ClientConfig.packageName→ PushConfig.bundleId passed to Client.registerForPushNotificationsClientConfig.useHmsForPushNotifications→ ConnectOptions.services passed to Client.connect
From v2:
val config = ClientConfig()
config.packageName = packageName
config.useHmsForPushNotifications = true
val client = Voximplant.getClientInstance(
executor,
applicationContext,
config,
)
To v3:
Client.connect(
options = ConnectOptions(node = Node).apply {
services = MobileServices.Google
},
callback = connectionCallback,
)
Client.registerForPushNotifications(
pushConfig = PushConfig(pushToken, applicationContext.packageName),
callback = pushCallback,
)
Capture SDK logs
Voximplant Android SDK v3 provides a more flexible interface to capture SDK logs with extended information such as a log timestamp, a thread id, and a throwable for exceptions.
Log collection configuration is done for all modules at once and is recommended on the SDK initialization.
From v2:
import com.voximplant.sdk.Voximplant
import com.voximplant.sdk.client.ILogListener
import com.voximplant.sdk.client.LogLevel
class SDKLogger() : ILogListener {
init {
Voximplant.setLogListener(this)
}
override fun onLogMessage(level: LogLevel, log: String) {
val message = "$level $message"
// save the log message to a file or send it to a remote service
}
}
To v3:
import com.voximplant.android.sdk.core.VICore
import com.voximplant.android.sdk.core.logging.LogLevel
import com.voximplant.android.sdk.core.logging.Logger
VICore.logging.logger = object : Logger {
override fun log(
level: LogLevel,
threadId: Int,
time: Date,
message: String,
throwable: Throwable?,
) {
val message = "$time $threadId $message $throwable"
// save the log message to a file or send it to a remote service
}
}
VICore.logging.level = LogLevel.Info
VICore.logging.enableLogcat = false