ECV Video Görüşme Android SDK [TR]
Giriş
ECV Görüntülü Görüşme Android SDK'sına hoş geldiniz! Bu SDK, Android uygulamalarınıza real-time görüntülü görüşme işlevselliğini kolayca entegre etmenizi sağlar. Signaling, WebRTC peer-to-peer bağlantıları, media streams ve çağrı state yönetiminin karmaşıklığını ele alarak uygulamanızın user interface (UI) ve iş mantığına odaklanmanıza olanak tanır.
Bu repository şunları içerir:
Ön Koşullar
Uygulamanızın ECV SDK library'sini ve gerekli dependency'lerini içermesi gerekir: WebRTC ve Autobahn. Bunları .aar dosyaları olarak alacaksınız.
-
Gerekli AAR Dosyaları:
- ECV SDK: (örn.
ECV-175-SDK-vX.Y.Z.aar) - Gerçek SDK dosya adınızla değiştirin. - WebRTC:
M134-libwebrtc.aar - Autobahn:
autobahn-android-legacy-20.2.1.aar
- ECV SDK: (örn.
-
Üç
.aardosyasını da projenizinlibsdizinine kopyalayın (appmodülü altında yoksa oluşturun). -
Uygulama düzeyindeki
build.gradle(veyabuild.gradle.kts) dosyanızı açın. -
libsdizinini, eğer zaten ekli değilse, repositories kısmına ekleyin:
// build.gradle.kts (Kotlin)
repositories {
google()
mavenCentral()
flatDir {
dirs("libs") // Bu satırı ekleyin
}
// Gerekirse diğer özel repoları ekleyin
}
```
-
AAR dosyalarını dependency olarak ekleyin:
// build.gradle (Groovy) dependencies { // ... diğer dependency'ler // --- ECV SDK ve Dependency'leri --- implementation(name: 'ECV-175-SDK-vX.Y.Z', ext: 'aar') // SDK dosya adınızla değiştirin implementation(name: 'M134-libwebrtc', ext: 'aar') implementation(name: 'autobahn-android-legacy-20.2.1', ext: 'aar') // android 21 uyumluluk için implementation 'net.sourceforge.streamsupport:streamsupport-cfuture:1.7.4' // eğer streamsupport-cfuture kullanılamaz ise onun yerine // compileOptions içerisinde `coreLibraryDesugaringEnabled true` yapılıp // aşağıdaki satır açılmalıdır // coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' // --- ECV SDK Dependency'leri Sonu --- // Ayrıca lifecycle-runtime-ktx'e de ihtiyacınız olabilir (aşağıya bakın) } // build.gradle.kts (Kotlin) dependencies { // ... diğer dependency'ler // --- ECV SDK ve Dependency'leri --- implementation(name = "ECV-175-SDK-vX.Y.Z", ext = "aar") // SDK dosya adınızla değiştirin implementation(name = "M134-libwebrtc", ext = "aar") implementation(name = "autobahn-android-legacy-20.2.1", ext = "aar") // android 21 uyumluluk için implementation("net.sourceforge.streamsupport:streamsupport-cfuture:1.7.4") // eğer streamsupport-cfuture kullanılamaz ise onun yerine // compileOptions içerisinde `coreLibraryDesugaringEnabled(true)` eklenip // aşağıdaki satır açılmalıdır // coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") // --- ECV SDK Dependency'leri Sonu --- // Ayrıca lifecycle-runtime-ktx'e de ihtiyacınız olabilir (aşağıya bakın) } -
Lifecycle Runtime Dependency Ekleme (Gerekirse):
ECV SDK, dahili olarakandroidx.lifecycle:lifecycle-runtime-ktxlibrary'sini (implementationdependency) kullanır. Bu birapidependency olmadığından, kullandığınız diğer library'ler (Compose, Navigation vb.) tarafından zaten dolaylı olarak dahil edilmiyorsa uygulamanızın bunu sağlaması gerekir.Bu dependency'nin uygulama düzeyindeki
build.gradle/build.gradle.ktsdosyanızda bulunduğundan emin olun:// build.gradle (Groovy) dependencies { // ... diğer dependency'ler implementation libs.androidx.lifecycle.runtime.ktx // Veya belirli version string'i, örn. "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0" // ECV SDK AAR'ları... } // build.gradle.kts (Kotlin) dependencies { // ... diğer dependency'ler implementation(libs.androidx.lifecycle.runtime.ktx) // Veya belirli version string'i, örn. "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0" // ECV SDK AAR'ları... }Not: Version catalog (
libs.) kullanmıyorsanız,libs.androidx.lifecycle.runtime.ktxifadesini"androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"gibi açık coordinate string'iyle değiştirin (projenizle uyumlu en son kararlı version'u kullanın). -
Gradle projenizi senkronize edin (
File > Sync Project with Gradle Files).
Dahil edilen demo uygulama (ecv175_android), SDK entegrasyonunun pratik bir örneğini sunar.
// Application class'ınızda veya merkezi bir yönetim object'inde bir yerde
import group.ccr.ecv.sdk.ECVSdk
import group.ccr.ecv.sdk.ECVSdkConfiguration
// 1. SDK Configuration Tanımlayın (Detaylar için Yapilandirma.md'ye bakın)
val sdkConfig = ECVSdkConfiguration(
serviceUrl = "SİZİN_SERVİS_URL_ADRESİNİZ", // Gerçek service URL'nizle değiştirin
// İsteğe bağlı olarak webrtcUrl, signalingUrl vb. override edin.
)
// 2. SDK Instance'ını Başlatın
// Merkezi olarak yönetilen tek bir instance'a sahip olmanız önerilir
val ecvSdk = ECVSdk(instanceName = "MyAppSdkInstance", config = sdkConfig)
// Bu instance'ı uygulamanız boyunca erişim için saklayın (örn. Application class, DI, Singleton)
// Örnek: MyApp.sdkInstance = ecvSdk
// Application class'ınızda veya merkezi bir yönetim object'inde bir yerde
import group.ccr.ecv.sdk.ECVSdk;
import group.ccr.ecv.sdk.ECVSdkConfiguration;
// 1. SDK Configuration Tanımlayın (Detaylar için Yapilandirma.md'ye bakın)
ECVSdkConfiguration sdkConfig = new ECVSdkConfiguration(
/* serviceUrl */ "SİZİN_SERVİS_URL_ADRESİNİZ", // Gerçek service URL'nizle değiştirin
);
// 2. SDK Instance'ını Başlatın
// Merkezi olarak yönetilen tek bir instance'a sahip olmanız önerilir
ECVSdk ecvSdk = new ECVSdk("MyAppSdkInstance", sdkConfig);
// Bu instance'ı uygulamanız boyunca erişim için saklayın (örn. Application class, DI, Singleton)
// Örnek: MyApp.sdkInstance = ecvSdk;
Temel Kavramlar
Demo uygulama şu temel flow'u izler:
Bu kılavuz, ECV Android SDK'sının temel component'lerinin kullanımı hakkında ayrıntılı bilgi sağlar.
1. Initialization (ECVSdk)
ECVSdk class'ı, SDK için birincil giriş noktasıdır. Genellikle uygulamanızın lifecycle'ı için tek bir instance oluşturursunuz.
Instance Oluşturma:
import group.ccr.ecv.sdk.ECVSdk
import group.ccr.ecv.sdk.ECVSdkConfiguration
// Configuration (Detaylar için Yapilandirma.md'ye bakın)
val sdkConfig = ECVSdkConfiguration(
serviceUrl = "SİZİN_SERVİS_URL_ADRESİNİZ",
)
// SDK Instance'ı Oluşturma
// Loglama/debug amacıyla açıklayıcı bir ad kullanın
val sdkInstance = ECVSdk(instanceName = "MyAppSdkInstance", config = sdkConfig)
// --- SDK Instance'ına Erişme ---
// Bu instance'ı uygun şekilde yönetmek çok önemlidir. Yaygın patterns:
// 1. Application alt class'ınızda saklayın:
// class MyApp : Application() {
// lateinit var sdkInstance: ECVSdk
// override fun onCreate() {
// super.onCreate()
// sdkInstance = ECVSdk(...)
// }
// }
// Erişim: (applicationContext as MyApp).sdkInstance
// 2. Dependency Injection (Hilt, Koin, vb.): ECVSdk'yı singleton olarak sağlayın.
// 3. Basit Singleton Object (Kotlin):
// object SdkManager {
// lateinit var instance: ECVSdk
// fun initialize(config: ECVSdkConfiguration) {
// instance = ECVSdk("MyAppSdkInstance", config)
// }
// }
// Erişim: SdkManager.instance
import group.ccr.ecv.sdk.ECVSdk;
import group.ccr.ecv.sdk.ECVSdkConfiguration;
import android.app.Application;
import android.content.Context;
import android.util.Log;
// Configuration (Detaylar için Yapilandirma.md'ye bakın)
ECVSdkConfiguration sdkConfig = new ECVSdkConfiguration(
/* serviceUrl */ "SİZİN_SERVİS_URL_ADRESİNİZ",
// ... webrtcUrl, signalingUrl gibi diğer parameters ...
);
// SDK Instance'ı Oluşturma
// Loglama/debug amacıyla açıklayıcı bir ad kullanın
ECVSdk sdkInstance = new ECVSdk("MyAppSdkInstance", sdkConfig);
// --- SDK Instance'ına Erişme ---
// Bu instance'ı uygun şekilde yönetmek çok önemlidir. Yaygın patterns:
// 1. Application alt class'ınızda saklayın:
// public class MyApp extends Application {
// private ECVSdk sdkInstance;
// @Override
// public void onCreate() {
// super.onCreate();
// ECVSdkConfiguration config = new ECVSdkConfiguration(...);
// sdkInstance = new ECVSdk("MyAppSdkInstance", config);
// }
// public ECVSdk getSdkInstance() {
// return sdkInstance;
// }
// }
// Erişim: ((MyApp) getApplicationContext()).getSdkInstance();
// 2. Dependency Injection (Dagger, Hilt): ECVSdk'yı singleton olarak sağlayın.
// 3. Basit Singleton Sınıfı (Java):
// public class SdkManager {
// private static ECVSdk instance;
// public static synchronized void initialize(Context context, ECVSdkConfiguration config) { // Context eklendi
// if (instance == null) {
// try {
// instance = new ECVSdk("MyAppSdkInstance", config);
// Log.i("SdkManager", "SDK Initialized");
// } catch (Exception e) {
// Log.e("SdkManager", "SDK Initialization failed", e);
// }
// }
// }
// public static ECVSdk getInstance() {
// if (instance == null) {
// throw new IllegalStateException("SdkManager not initialized. Call initialize first.");
// }
// return instance;
// }
// }
// Erişim: SdkManager.getInstance();
Temel Components:
Bir VideoCall object'i, başlatmadan sonlandırmaya kadar tek bir çağrı session'ını temsil eder.
Çağrı Oluşturma:
// sdkInstance'ın kullanılabilir olduğu varsayılır
val videoCall: VideoCall = sdkInstance.createCall() // API call yoluyla rtcId oluşturur
// VEYA test/bilinen bir rtcId ile katılma için:
// val testVideoCall: VideoCall = sdkInstance.createTestCall() // rtcId'yi otomatik olarak oluşturmaz
// testVideoCall.setRtcId("MEVCUT_RTC_ID")
// sdkInstance'ın kullanılabilir olduğu varsayılır
VideoCall videoCall = sdkInstance.createCall(); // API call yoluyla rtcId oluşturur
// VEYA test/bilinen bir rtcId ile katılma için:
// VideoCall testVideoCall = sdkInstance.createTestCall(); // rtcId'yi otomatik olarak oluşturmaz
// testVideoCall.setRtcId("MEVCUT_RTC_ID");
Çağrı Lifecycle & Anahtar Method'lar:
- `videoCall.rtcId`: Benzersiz ID'ye erişin (`GotRtcId` event'inden sonra kullanılabilir).
-
Dialing (
start): Çağrı parameters ile backend'le iletişim kurarak çağrı process'ini başlatır. Bu genellikle arayanı bir queue'ya koyar.videoCall.start(dialParameters: DialCallParameters):DialCallParametersiçinYapilandirma.mdye bakın.DialSuccessveyaErrorevents dinleyin.OnQueueevent'i, çağrının bir agent beklediğini gösterir.
-
Agent Bağlantısı:
AgentConnectedevent'i, bir agent çağrıyı kabul ettiğinde tetiklenir.videoCall.agentConnectedState: Agent'ın bağlı olup olmadığını gösteren Boolean flag'i.
-
WebRTC Bağlantısı (
connect):AgentConnectedsonrasında, peer-to-peer media bağlantısını kurarsınız.videoCall.connect(parameters: ConnectParameters? = null): WebRtcConnection:WebRtcConnectionobject'ini oluşturur veya alır.ConnectParametersiçinYapilandirma.mdye bakın.
-
Medyayı Başlatma (
startRoom):WebRtcConnection'a sahip olduğunuzda, WebRTC setup process'ini başlatmanız gerekir.webRtcConnection.startRoom(context: Context): PeerConnection'ı başlatır, media tracks ayarlar ve signaling process'ini başlatır.
-
Medya Kontrolü:
WebRtcConnectioninstance'ındaki method'ları kullanın (sonraki bölüme bakın). -
Çağrıyı Sonlandırma (
hangup): Çağrı session'ını sonlandırır ve resources temizler.videoCall.hangup(): Kapatma process'ini başlatır. Bu, user kapattığında veya çağrı başka nedenlerle (örn. uzaktan kapatma, error) sona erdiğinde çağrılmalıdır.Hangupevent'ini (hangup()çağrıldığında tetiklenir) veyaCallEndedevent'ini (genellikle uzak taraf veya errors tarafından tetiklenir) dinleyin.
Çağrı ekranından uzaklaşırsanız ve daha sonra VideoCall instance'ını almanız gerekirse (örn. QueueScreen veya CallScreen'de), rtcId'yi kullanın:
val rtcId: String = // mevcut çağrı için rtcId'yi alın ...
val existingCall = sdkInstance.getVideoCall(rtcId)
if (existingCall != null) {
// Çağrı instance'ını kullanın
} else {
// Çağrı bulunamadı, uygun şekilde ele alın (örn. geri gidin)
}
String rtcId = // mevcut çağrı için rtcId'yi alın ...
VideoCall existingCall = sdkInstance.getVideoCall(rtcId);
if (existingCall != null) {
// Çağrı instance'ını kullanın
} else {
// Çağrı bulunamadı, uygun şekilde ele alın (örn. geri gidin)
}
Önemli: SDK, dahili olarak rtcId kullanarak VideoCall instance'larına bir reference tutar. hangup() çağrılması, sonunda bu reference'ın temizlenmesine ve potansiyel olarak kaldırılmasına yol açacaktır.
3. WebRTC Connection (WebRtcConnection)
Bu class, PeerConnection, media tracks ve Renderers dahil olmak üzere düşük seviyeli WebRTC ayrıntılarını yönetir. videoCall.connect() aracılığıyla elde edersiniz.
Anahtar Method'lar:
SurfaceViewRenderer, özellikle AndroidView kullanan Jetpack Compose'da dikkatli lifecycle management gerektirir.
// AndroidView bloğu içinde basitleştirilmiş örnek
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.background
import androidx.compose.material3.Text // Veya androidx.compose.material.Text
import android.util.Log
import group.ccr.ecv.sdk.webRtc.WebRtcConnection // Import path'i doğrulayın
import org.webrtc.RendererCommon
import org.webrtc.SurfaceViewRenderer
// webRtcConnection ve isLocalView'in composable scope'unda mevcut olduğu varsayılır
@Composable
fun VideoDisplay(webRtcConnection: WebRtcConnection?, isLocalView: Boolean, modifier: Modifier = Modifier) {
if (webRtcConnection == null) {
// Connection hazır olmadığında durumu ele alın
Box(modifier.background(Color.Gray)) { Text("Connecting...") }
return
}
AndroidView(
modifier = modifier,
factory = { ctx ->
SurfaceViewRenderer(ctx).apply {
// null check factory içinde de yapılabilir
webRtcConnection?.eglBase?.eglBaseContext?.let { eglContext ->
init(eglContext, null)
setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
} ?: Log.e("VideoDisplay", "EGL Context was null in factory (isLocal: $isLocalView)")
}
},
update = { surfaceViewRenderer ->
// Yerel mi yoksa uzak mı ekleneceğine karar verme mantığı
webRtcConnection?.let { conn -> // Null check
surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL) // Gerekirse yeniden uygulayın
if (isLocalView) {
conn.detachRemoteView(surfaceViewRenderer) // Önce temizleyin
conn.attachLocalView(surfaceViewRenderer)
} else {
conn.detachLocalView(surfaceViewRenderer) // Önce temizleyin
conn.attachRemoteView(surfaceViewRenderer)
}
} ?: Log.w("VideoDisplay", "webRtcConnection was null in update (isLocal: $isLocalView)")
},
onRelease = { surfaceViewRenderer ->
// RELEASE ÖNCESİ DETACH ESASTIR
Log.d("VideoRenderer", "Releasing SurfaceViewRenderer (isLocal: $isLocalView)")
webRtcConnection?.let { conn ->
conn.detachLocalView(surfaceViewRenderer)
conn.detachRemoteView(surfaceViewRenderer)
}
surfaceViewRenderer.release() // WebRTC resources serbest bırakın
}
)
}
// Android Fragment veya Activity içinde basitleştirilmiş örnek
// webRtcConnection ve surfaceViewRenderer'ın uygun şekilde initialize edilmiş member variables olduğu varsayılır
import org.webrtc.RendererCommon;
import org.webrtc.SurfaceViewRenderer;
import group.ccr.ecv.sdk.webRtc.WebRtcConnection; // Import path'i doğrulayın
// onCreateView veya onViewCreated içinde:
// SurfaceViewRenderer surfaceViewRenderer = binding.surfaceView; // ViewBinding'den
// if (webRtcConnection != null && webRtcConnection.getEglBase() != null && webRtcConnection.getEglBase().getEglBaseContext() != null) {
// surfaceViewRenderer.init(webRtcConnection.getEglBase().getEglBaseContext(), null);
// surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
// if (isLocalView) { // isLocalView UI mantığı tarafından belirlenir
// webRtcConnection.attachLocalView(surfaceViewRenderer);
// } else {
// webRtcConnection.attachRemoteView(surfaceViewRenderer);
// }
// } else {
// // Log error: Connection or EGL Context is null
// }
// onDestroyView içinde:
// if (webRtcConnection != null && surfaceViewRenderer != null) {
// // Hem local hem de remote için detach çağrılmalı, hangisinin attach edildiğini bilmeseniz bile güvenlidir
// webRtcConnection.detachLocalView(surfaceViewRenderer);
// webRtcConnection.detachRemoteView(surfaceViewRenderer);
// surfaceViewRenderer.release();
// }
4. Event Handling
SDK, state değişikliklerini ve olayları eşzamansız olarak Events aracılığıyla iletir.
Events Abone Olma:
```
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import group.ccr.ecv.sdk.videoCall.VideoCall // VideoCall class'ınızı import edin
import group.ccr.ecv.sdk.videoCall.events.CallEventTypes
import kotlinx.coroutines.flow.collect
import android.util.Log
// Bir Composable içinde (LaunchedEffect kullanarak)
@Composable
fun MyCallScreen(videoCall: VideoCall?) { // VideoCall instance'ını iletin
LaunchedEffect(videoCall) { // videoCall instance'ına key'lenir
videoCall?.eventFlow?.collect { event ->
Log.d("MyApp", "SDK Event Alındı: ${event.type} - ${event.detail}")
when (event.type) {
CallEventTypes.GotRtcId -> { /* rtcId'yi saklayın: event.detail */ }
CallEventTypes.DialSuccess -> { /* Çağrı aranıyor */ }
CallEventTypes.OnQueue -> { /* Temsilci bekleniyor */ }
CallEventTypes.AgentConnected -> { /* Çağrı ekranına gidin, videoCall.connect() çağırın */ }
CallEventTypes.WebRtcConnected -> { /* Media bağlantısı kuruldu */ }
CallEventTypes.VideoMuted -> { /* Yerel video sessize alma UI state'ini güncelleyin */ }
CallEventTypes.RemoteVideoMuted -> { /* Uzak video sessize alma UI state'ini güncelleyin */ }
CallEventTypes.StartHold -> { /* "Beklemede" UI'sini gösterin */ }
CallEventTypes.CallEnded -> { /* Geri gidin, temizleyin. Detay: event.detail */ }
CallEventTypes.Error -> { /* Hata mesajı gösterin: event.error / event.detail */ }
// ... diğer ilgili events'i ele alın
else -> { /* İsteğe bağlı: Ele alınmayan events'i loglayın */ }
}
}
}
// ... Composable UI'nin geri kalanı ...
}
// Bir Activity/Fragment içinde (lifecycleScope kullanarak)
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// // ... setup ...
// val videoCall = // ... VideoCall instance'ını alın ...
//
// lifecycleScope.launch {
// repeatOnLifecycle(Lifecycle.State.STARTED) {
// videoCall?.eventFlow?.collect { event ->
// // Event'i yukarıdaki gibi ele alın
// }
// }
// }
// }
```
-
Java (Callback):
subscribemethod'unu birLifecycleOwnerve birEventCallbackile kullanın.import androidx.lifecycle.LifecycleOwner; // örn. Activity veya Fragment'ınız import group.ccr.ecv.sdk.EventCallback; import group.ccr.ecv.sdk.videoCall.VideoCall; import group.ccr.ecv.sdk.videoCall.events.CallEventTypes; import group.ccr.ecv.sdk.videoCall.events.VideoCallEvent; import android.util.Log; // Activity onCreate veya Fragment onViewCreated içinde LifecycleOwner lifecycleOwner = this; // Activity/Fragment'ınız LifecycleOwner uygular VideoCall videoCall = // ... videoCall instance'ını alın ... if (videoCall != null) { videoCall.subscribe(lifecycleOwner, new EventCallback() { @Override public void onEvent(VideoCallEvent event) { // UI güncellemelerini main thread'de yapmak genellikle daha güvenlidir runOnUiThread(() -> { Log.d("MyApp", "SDK Event Alındı: " + event.getType() + " - " + event.getDetail()); // event.getType() üzerinde switch ifadesi veya if-else if kullanın if (event.getType() == CallEventTypes.GotRtcId) { // rtcId'yi saklayın: event.getDetail() } else if (event.getType() == CallEventTypes.AgentConnected) { // Çağrı ekranına gidin, videoCall.connect() çağırın } else if (event.getType() == CallEventTypes.CallEnded) { // Geri gidin, temizleyin. Detay: event.getDetail() } else if (event.getType() == CallEventTypes.Error) { // Hata mesajı gösterin: event.getError() / event.getDetail() } // ... diğer ilgili events'i ele alın }); } }); } // Alternatif olarak, ECVSdkModules aracılığıyla global events'e abone olun // sdkInstance.getModules().subscribe(lifecycleOwner, new EventCallback() { ... });
Daha kapsamlı bir liste için Olaylar_ve_Durum_Yonetimi.md dosyasına bakın, ancak bazı kritik olanlar şunlardır:
Hatalar genellikle Error event'i (CallEventTypes.Error) aracılığıyla bildirilir. VideoCallEvent object'i şunları içerir:
Yapılandırma Kılavuzu
Bu kılavuz, ECV SDK ve bireysel çağrılar için mevcut çeşitli configuration seçeneklerini açıklar.
1. SDK Initialization Configuration (ECVSdkConfiguration)
Bu configuration, ana ECVSdk instance'ını oluştururken sağlanır ve SDK'nın backend infrastructure ile iletişim kurması için temel bağlantı parameters tanımlar.
Class: group.ccr.ecv.sdk.ECVSdkConfiguration
Parameters:
- Örnek: `"https://sirketiniz.ecv.backend.com"`
-
webrtcUrl: String(İsteğe bağlı, defaultserviceUrl): Özellikle WebRTC signaling ile ilgili HTTP requests (/joinendpoint'i gibi) için kullanılan temel URL. WebRTC signaling infrastructure'ınız ayrı olarak host ediliyorsa, burada belirtin.- Örnek:
"https://sirketiniz.ecv.webrtc.com"
- Örnek:
-
signalingUrl: String(İsteğe bağlı, defaultserviceUrl): WebSocket signaling server için temel URL. SDK,http/httpsyerinews/wsskullanacaktır. WebSocket server'ınız ayrı olarak host ediliyorsa, burada belirtin.- Örnek:
"https://sirketiniz.ecv.websocket.com"
- Örnek:
-
useQueueSocket: Boolean(İsteğe bağlı, defaulttrue): Queue state güncellemelerini (AgentConnectedgibi) alma mekanizmasını belirler.true(ECV v1.75+ için önerilir): Real-time güncellemeler için özel bir WebSocket (QueueSocket) kullanır. Bu, ECV backend infrastructure'ınızın 1.75 veya daha yüksek bir version olmasını gerektirir. Polling'e kıyasla daha hızlı bildirimler sağlar.
import group.ccr.ecv.sdk.ECVSdkConfiguration
// ECV v1.75+ backend için configuration (tercih edilen WebSocket kullanarak)
val configV175 = ECVSdkConfiguration(
serviceUrl = "https://prod-v175.ecv.example.com",
)
import group.ccr.ecv.sdk.ECVSdkConfiguration;
// ECV v1.75+ backend için configuration (tercih edilen WebSocket kullanarak)
// Constructor'ın serviceUrl aldığını ve diğer params'ın default olduğunu veya daha sonra ayarlandığını varsayar.
ECVSdkConfiguration configV175 = new ECVSdkConfiguration(
/* serviceUrl */ "https://prod-v175.ecv.example.com"
);
2. Çağrı Başlatma Parameters (DialCallParameters)
Bu parameters, belirli bir çağrı session'ını başlatmak için videoCall.start() çağrıldığında sağlanır.
Class: group.ccr.ecv.sdk.videoCall.api.DialCallParameters
Parameters:
- Örnek: `mapOf("customerId" to "12345", "product" to "Premium Account")`
-
acdAttributes: Map<String, String>(İsteğe bağlı, defaultemptyMap()): Backend'deki ACD (Automatic Call Distributor) routing mantığı için tasarlanmış özel attributes.- Örnek:
mapOf("skill" to "Billing", "language" to "en-US")
- Örnek:
-
queue: String?(İsteğe bağlı, defaultnull): Çağrıyı yönlendirmek için belirli queue veya workgroup adı.nullveya boşsa, backend'deki default routing kuralları geçerlidir.- Örnek:
"SalesQueue"
- Örnek:
-
customPayload: Map<String, String>(İsteğe bağlı, defaultemptyMap()): Özel backend entegrasyonunuzun gerektirdiği diğer custom data göndermek için esnek bir map. -
additionalHeaders: Map<String, String>(İsteğe bağlı, defaultemptyMap()):/api/dialexrequest'ine custom HTTP headers eklemeye izin verir. Bu, authentication tokens veya diğer metadata için kullanılabilir.- Örnek:
mapOf("Authorization" to "Bearer YOUR_TOKEN", "X-Tenant-ID" to "TenantA")
- Örnek:
import group.ccr.ecv.sdk.videoCall.api.DialCallParameters
val params = DialCallParameters(
callerName = "Jane Doe",
attributes = mapOf(
"accountNumber" to "ACC9876",
"lastInteraction" to "2023-10-26"
),
queue = "SupportQueue",
additionalHeaders = mapOf("X-Auth-Token" to "user_session_token")
)
// videoCall'un VideoCall instance'ı olduğu varsayılır
videoCall.start(params)
import group.ccr.ecv.sdk.videoCall.api.DialCallParameters;
import java.util.HashMap;
import java.util.Map;
Map<String, String> attributes = new HashMap<>();
attributes.put("accountNumber", "ACC9876");
attributes.put("lastInteraction", "2023-10-26");
Map<String, String> headers = new HashMap<>();
headers.put("X-Auth-Token", "user_session_token");
DialCallParameters params = new DialCallParameters(
/* callerName */ "Jane Doe",
/* attributes */ attributes,
/* acdAttributes */ new HashMap<>(), // Boş map
/* queue */ "SupportQueue",
/* customPayload */ new HashMap<>(), // Boş map
/* additionalHeaders */ headers
);
// videoCall'un VideoCall instance'ı olduğu varsayılır
videoCall.start(params);
3. WebRTC Connection Parameters (ConnectParameters)
Bu parameters, videoCall.connect() çağrıldığında isteğe bağlı olarak sağlanır ve WebRTC peer connection davranışını ince ayarlar. Çoğu parameter'ın mantıklı default değerleri vardır.
Class: group.ccr.ecv.sdk.webRtc.ConnectParameters
Anahtar Parameters (Defaults gösterilmiştir):
- `candidateFilteringMode: CandidateFilteringMode = CandidateFilteringMode.ALL`: Hangi ICE candidates kullanılacağını kontrol eder (All, Relay only, Host only).
- `connectionTimeout: Int = 10000`: Genel WebRTC bağlantı denemesi için timeout (ms) (`WebRtcConnection`'daki manuel timer tarafından kullanılır).
- `joinHttpConnectTimeout: Int = 3000`: `/join` request'i için HTTP connect timeout (ms).
- `joinHttpReadTimeout: Int = 3000`: `/join` request'i için HTTP read timeout (ms).
- `additionalHeaders: Map<String, String> = emptyMap()`: `/join` request'ine ve WebSocket signaling connection handshake'ine eklenen custom HTTP headers. WebRTC/Signaling infrastructure'ına ulaşması gereken authentication tokens için kullanışlıdır.
-
Media Control & Behavior:
videoCallEnabled: Boolean = true: Video tracks kurulup kurulmayacağı. Audio-only çağrılar içinfalseolarak ayarlayın.disableVideo: Boolean = false: Gerekirse video gönderme/alma işlemini tamamen devre dışı bırakmak için üst düzey flag (genelliklevideoCallEnabledyeterlidir).disableAudio: Boolean = false: Gerekirse ses gönderme/alma işlemini tamamen devre dışı bırakmak için üst düzey flag.useSpeakerPhone: Boolean = true: Çağrıyı speakerphone kullanarak başlatın.autoSwitchToEarPiece: Boolean = false: Proximity sensor tetiklendiğinde (telefon kulağa yakın) otomatik olarak earpiece'e geçin.autoHandleCameraSwitch: Boolean = true: Belirli events meydana geldiğinde (örn. agent arka kamera ile fotoğraf istediğinde) SDK'nın kameraları otomatik olarak değiştirmesine izin verir.autoHandleHoldSideEffects: Boolean = true: Agent çağrıyı hold'a aldığında veya devam ettirdiğinde yerel ses/videoyu otomatik olarak sessize alır/açar.
-
Video Configuration:
videoWidth: Int = 480/videoHeight: Int = 640: İstenen video resolution.videoFps: Int = 0(Default ~30): İstenen video frame rate.videoMaxBitrate: Int = 0(WebRTC default): Video encoding için maksimum bitrate (kbps).videoCodec: String = "VP8": Tercih edilen video codec ("VP8", "VP9", "H264").videoCodecHwAcceleration: Boolean = true: Varsa hardware acceleration'ı etkinleştirin.
-
Audio Configuration:
audioCodec: String = "OPUS": Tercih edilen audio codec ("OPUS", "ISAC").audioStartBitrate: Int = 0(WebRTC default): Başlangıç audio bitrate (kbps).noAudioProcessing: Boolean = false: Platform audio processing'i (AEC, AGC, NS) devre dışı bırakın.useOpenSLES: Boolean = false: OpenSL ES audio backend'ini kullanın (Android'e özgü).disableBuiltInAEC: Boolean = false/disableBuiltInNS: Boolean = false: Varsa hardware AEC/NS'yi devre dışı bırakın.
-
Debugging:
tracing: Boolean = false: WebRTC dahili tracing'i etkinleştirin.aecDump: Boolean = false: AEC diagnostic data'yı kaydedin.saveInputAudioToFile: Boolean = false: Ham input audio'yu kaydedin (useOpenSLES = falsegerektirir).enableRtcEventLog: Boolean = false: WebRTC events'i bir dosyaya loglayın.
-
Data Channel (
textChannelConfig):textChannelConfig: DataChannelConfig? = DataChannelConfig(...): FLASH_ON/OFF, HOLD/PICKUP vb. komutlar için kullanılan yerleşik text data channel configuration'ı. Genellikle default bırakılır. Detaylar içinDataChannelConfigclass'ına bakın.
import group.ccr.ecv.sdk.webRtc.ConnectParameters
import group.ccr.ecv.sdk.webRtc.CandidateFilteringMode
// Çoğu default genellikle yeterlidir. Yalnızca belirli parameters override edin.
val customConnectParams = ConnectParameters(
videoWidth = 1280,
videoHeight = 720,
videoCodec = "H264",
candidateFilteringMode = CandidateFilteringMode.RELAY, // Örnek: Yalnızca TURN servers kullan
additionalHeaders = mapOf("X-WebRTC-Auth" to "webrtc_token")
)
// Custom parameters kullanarak WebRtcConnection'ı alın
val webRtcConnection = videoCall.connect(parameters = customConnectParams)
webRtcConnection.startRoom(context)
import group.ccr.ecv.sdk.webRtc.ConnectParameters;
import group.ccr.ecv.sdk.webRtc.CandidateFilteringMode;
import group.ccr.ecv.sdk.webRtc.DataChannelConfig; // Varsayalım ki bu var ve gerekli
import java.util.HashMap;
import java.util.Map;
// Constructor (varsa) veya defaults kullanarak ConnectParameters oluşturun
ConnectParameters customConnectParams = new ConnectParameters(
/* videoCallEnabled */ true,
/* disableVideo */ false,
/* disableAudio */ false,
/* videoWidth */ 1280, // Custom width
/* videoHeight */ 720, // Custom height
/* videoFps */ 0, // Default
/* videoMaxBitrate */ 0, // Default
/* videoCodec */ "H264", // Custom codec
/* videoCodecHwAcceleration */ true, // Default
/* videoFlexfecEnabled */ false, // Default
/* audioCodec */ "OPUS", // Default
/* audioStartBitrate */ 0, // Default
/* noAudioProcessing */ false, // Default
/* useOpenSLES */ false, // Default
/* disableBuiltInAEC */ false, // Default
/* disableBuiltInNS */ false, // Default
/* disableBuiltInAGC */ false, // Bu param'ın constructor'da olup olmadığını kontrol edin
/* disableWebRtcAGCAndHPF */ false, // Default
/* candidateFilteringMode */ CandidateFilteringMode.RELAY, // Custom filtering
/* connectionTimeout */ 10000, // Default
/* joinHttpConnectTimeout */ 3000, // Default
/* joinHttpReadTimeout */ 3000, // Default
/* useSpeakerPhone */ true, // Default
/* autoSwitchToEarPiece */ false, // Default
/* autoHandleCameraSwitch */ true, // Default
/* autoHandleHoldSideEffects */ true, // Default
/* additionalHeaders */ new HashMap<String, String>() {{ // Custom headers
put("X-WebRTC-Auth", "webrtc_token");
}},
/* tracing */ false, // Default
/* aecDump */ false, // Default
/* saveInputAudioToFile */ false, // Default
/* enableRtcEventLog */ false, // Default
/* textChannelConfig */ new DataChannelConfig(true, "sendTextChannel", -1, false, "", true, -1, -1) // Örnek default config
);
// Custom parameters kullanarak WebRtcConnection'ı alın
WebRtcConnection webRtcConnection = videoCall.connect(customConnectParams);
webRtcConnection.startRoom(context);
// Not: Yukarıdaki Java örneği, tüm bu fields eşleşen bir constructor olduğunu varsayar.
// ConnectParameters'ın gerçek constructor signature'ını doğrulayın.
// Yalnızca default bir constructor varsa, default object'i oluşturun
// ve public ise veya setters varsa fields değiştirin.
4. Demo Uygulama Environment Configuration
Demo Uygulama, farklı SDK configurations kalıcı olarak yönetmeye olanak tanıyan bir EnvironmentSelectorScreen içerir. Bu öncelikle test ve tanıtım içindir.
Giriş
Bu kılavuz, sağlanan Android Demo Uygulaması (ecv175_android) boyunca size yol gösterir. Birincil amacı, ECV Görüntülü Görüşme SDK'sını işlevsel, ancak basitleştirilmiş bir uygulamada nasıl entegre edeceğinizi ve kullanacağınızı göstermektir.
Demo, UI'si için Jetpack Compose kullanır ve yaygın bir navigation pattern'ini izler. UI'nin kendisi production'a hazır olmasa da, SDK ile etkileşim şekli değerli örnekler sunar. Java kod parçacıkları, SDK kullanımını göstermek amacıyla kavramsal olarak sağlanmıştır, ancak geleneksel bir Java/XML uygulamasındaki UI implementation önemli ölçüde farklılık gösterecektir.
Anahtar ekranları ve SDK'nın yeteneklerinden nasıl yararlandıklarını inceleyeceğiz:
Gerçek bir uygulamada, initialize edilmiş ECVSdk instance'ını ve potansiyel olarak o anda aktif olan VideoCall instance'ını farklı ekranlardan (Activities/Fragments/Composables) erişilebilmesi için yönetmenin bir yoluna ihtiyacınız vardır.
Demo uygulama bu amaçla AppState adında basit bir Kotlin object kullanır:
Kotlin Örneği (AppState.kt konsepti):
// AppState.kt'den basitleştirilmiş konsept
object AppState {
var sdkInstance: ECVSdk? = null
// ... diğer demo'ya özgü state ...
fun initializeSdk(context: Context, environment: Environment) {
// ... (daha önce gösterilen mantık) ...
sdkInstance = ECVSdk(instanceName = "DemoSdk-${environment.name}", config = sdkConfig)
// ...
}
}
// Erişim: AppState.sdkInstance
Java Eşdeğeri (örn. Application class'ında veya Singleton'da):
import android.app.Application;
import android.content.Context;
// Init logic için gerekliyse Environment/Configuration class'larınızı import edin
import group.ccr.ecv.sdk.ECVSdk;
import group.ccr.ecv.sdk.ECVSdkConfiguration;
import android.util.Log; // Log için import
public class MyApp extends Application {
private static ECVSdk sdkInstance;
@Override
public void onCreate() {
super.onCreate();
// SDK'yı burada veya talep üzerine initialize edin
// initializeSdk(this, /* Configuration kaynağınızı iletin */);
}
// SDK'yı initialize etme method'u (örnek)
public static synchronized void initializeSdk(Context context, ECVSdkConfiguration config) {
if (sdkInstance == null) {
try {
sdkInstance = new ECVSdk("MyAppSdkInstance", config);
Log.i("MyApp", "SDK Initialized");
} catch (Exception e) {
Log.e("MyApp", "SDK Initialization failed", e);
}
}
}
// Kolay erişim için static getter
public static ECVSdk getSdkInstance() {
if (sdkInstance == null) {
// İsteğe bağlı olarak burada initialize edin veya bir error fırlatın
throw new IllegalStateException("SDK not initialized. Call initializeSdk first.");
}
return sdkInstance;
}
}
// Erişim: MyApp.getSdkInstance()
Anahtar Çıkarımlar:
Amaç: Farklı backend configurations (Staging, Production, Local Dev gibi) test etmek için demo'ya özgü ekran. Configuration'ları yükleme/kaydetme ve temel CRUD işlemlerini gösterir.
SDK Etkileşimi:
3. Çağrı Başlatma (MainScreen.kt / Eşdeğer Activity/Fragment)
Amaç: Bir environment yapılandırıldıktan sonra görüntülü görüşme başlatmak için giriş noktası görevi görür. Permissions yönetir, temel çağrı data'sını toplar ve çağrı process'ini başlatır.
SDK Etkileşimi ve Kod Önemli Noktaları:
- `requiredPermissions` tanımlar (Kamera, Mikrofon, S+ üzerinde Bluetooth Connect).
- Permissions istemek için `rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions())` kullanır.
- Durumu doğrulamak için `checkAllPermissions()` kullanır.
- "START" butonu yalnızca `allPermissionsGranted` true olduğunda etkindir.
```
val requiredPermissions = remember { /* ... kamera, mic, bluetooth ... */ }
var allPermissionsGranted by remember { mutableStateOf(checkAllPermissions(context, requiredPermissions)) }
val multiplePermissionsLauncher = rememberLauncherForActivityResult(...) { /* ... allPermissionsGranted'ı güncelle ... */ }
LaunchedEffect(Unit) {
if (!allPermissionsGranted) {
multiplePermissionsLauncher.launch(requiredPermissions.toTypedArray())
}
}
// ... Buton allPermissionsGranted'a göre etkinleştirilir ...
```
-
Data Toplama:
callerNameiçinOutlinedTextField.AppState.currentConfig?.callerAttributeSelectionsboş değilseSimpleSelectDropdown(bir demo helper) kullanılır,callerAttributesmap'ini doldurur.
-
Çağrıyı Başlatma: ("START" butonunun
onClick'i içinde)-
Çağrı Oluşturma: SDK'dan bir
VideoCallinstance'ı alın.val call = AppState.sdkInstance?.createCall() if (call == null) { // SDK'nın initialize edilmediği durumu ele alın Toast.makeText(context, "SDK not initialized", Toast.LENGTH_SHORT).show() return@Button // veya return@launch if in coroutine scope } -
(Önerilen)
GotRtcIdBekleme: Demo,createCall'dan kısa süre sonrartcId'nin hazır olacağına örtük olarak güvense de, sağlam kod genellikle event'i bekler.// onClick'ten coroutine scope kullanarak scope.launch { var rtcId: String? = null try { // Event'i VEYA bir error bekleyin val initEvent = call.eventFlow.first { it.type == CallEventTypes.GotRtcId || it.type == CallEventTypes.Error } if (initEvent.type == CallEventTypes.GotRtcId) { rtcId = initEvent.detail Log.d("MainScreen", "Got rtcId: $rtcId") } else { throw Exception("Error getting rtcId: ${initEvent.error?.message ?: initEvent.detail}") } } catch (e: Exception) { Log.e("MainScreen", "Failed to get rtcId", e) Toast.makeText(context, "Error initializing call: ${e.message}", Toast.LENGTH_LONG).show() return@launch // rtcId başarısız olursa dur } if (rtcId == null) return@launch // Exception handling doğruysa olmamalı // --- Dialing ile devam edin --- val dialParams = DialCallParameters( callerName = callerName, // UI State'inden attributes = callerAttributes // UI State'inden (Map<String, String>) // queue = "OptionalQueueName" // Gerekirse ) call.start(dialParams) // Dialing process'ini başlatın // Dial process'ini başlattıktan sonra navigate edin // QueueScreen AgentConnected'ı beklemeyi ele alacaktır navController.navigate("queue/$rtcId") Log.d("MainScreen", "Navigated to queue for rtcId: $rtcId") } -
DialCallParametersOluşturma: UI state'inden data toplayın (callerName,callerAttributes). -
start()Çağırma:call.start(dialParams)request'i backend'e gönderir. -
Navigate: Başarılı başlatma üzerine (
AgentConnectedöncesi),rtcId'yi ileterek queue ekranına gidin.navController.navigate("queue/${call.rtcId}").
Java Eşdeğeri (örn. bir Activity
onClick'inde):// SDK instance'ını alın (örn. Application class'ından) ECVSdk sdk = MyApp.getSdkInstance(); if (sdk == null) { Toast.makeText(this, "SDK not initialized", Toast.LENGTH_SHORT).show(); return; } // Çağrıyı oluşturun VideoCall videoCall = sdk.createCall(); // UI data'sını alın String callerName = editTextCallerName.getText().toString(); Map<String, String> callerAttributes = getAttributesFromUI(); // Sizin mantığınız burada // Not: Java'da GotRtcId beklemek, Kotlin'deki gibi bir flow'u doğrudan 'await' edemediğiniz için // bir callback mekanizması veya dikkatli state management gerektirir. // Basitleştirilmiş bir yaklaşım (daha az sağlam) delay kabul edilebilirse açık beklemeyi atlayabilir. // Daha sağlam bir yol, subscribe mekanizmasını kullanmayı içerir (aşağıda event handling bölümünde gösterilmiştir) // --- Daha iyi yaklaşım: Events'e Abone Olun --- // Activity'nin lifecycle scope'u içinde abone olun // (Subscribe pattern'i için aşağıdaki event handling bölümüne bakın) // GotRtcId için onEvent callback'inde: // String rtcId = event.getDetail(); // DialCallParameters dialParams = new DialCallParameters(callerName, callerAttributes, ...); // videoCall.start(dialParams); // // Queue Activity/Fragment'a gidin // Intent intent = new Intent(this, QueueActivity.class); // intent.putExtra("RTC_ID", rtcId); // startActivity(intent); // --- Bu kavramsal örnek için, GotRtcId'nin bir şekilde gerçekleştiğini varsayalım --- // --- ve sonra dial edip navigate edersiniz --- // DialCallParameters dialParams = new DialCallParameters(callerName, callerAttributes, ...); // videoCall.start(dialParams); // // rtcId'nin şimdi mevcut olduğunu varsayarsak (doğru event handling olmadan bu doğru olmayabilir) // Intent intent = new Intent(this, QueueActivity.class); // intent.putExtra("RTC_ID", videoCall.getRtcId()); // Başlangıçta boş olabilir! // startActivity(intent); -
Amaç: Çağrı queue'dayken, bir agent'ın bağlanmasını beklerken bir bekleme göstergesi görüntüler. SDK events'e göre navigation'ı yönetir.
SDK Etkileşimi ve Kod Önemli Noktaları:
**Kotlin (Demo):**
```
val rtcId: String = // navigation args'dan
var videoCall = AppState.sdkInstance?.getVideoCall(rtcId)
// Null ise ele alın
```
**Java Eşdeğeri:**
```
// Activity onCreate veya Fragment onViewCreated içinde
String rtcId = getIntent().getStringExtra("RTC_ID"); // Veya getArguments().getString(...)
VideoCall videoCall = MyApp.getSdkInstance().getVideoCall(rtcId);
if (videoCall == null) {
Toast.makeText(this, "Call not found", Toast.LENGTH_SHORT).show();
finish(); // Bu activity'yi kapatın
return;
}
// videoCall'u bir member variable olarak saklayın
this.videoCall = videoCall;
```
-
Event Handling: Belirli
videoCalliçin events toplayın/abone olun.Kotlin (Demo -
LaunchedEffect):LaunchedEffect(videoCall) { if (videoCall.agentConnectedState) { /* Navigate */ return@LaunchedEffect } videoCall.eventFlow.collect { event -> when (event.type) { CallEventTypes.AgentConnected -> { /* CallActivity'ye gidin */ } CallEventTypes.Error, CallEventTypes.Hangup, CallEventTypes.CallEnded -> { /* Main'e geri gidin */ } else -> {} } } }Java Eşdeğeri (
subscribekullanarak):// Activity onCreate veya Fragment onViewCreated içinde, videoCall alındıktan sonra LifecycleOwner lifecycleOwner = this; // Activity/Fragment bir LifecycleOwner'dır // Initial state'i kontrol edin if (videoCall.getAgentConnectedState()) { navigateToCallScreen(rtcId); // Sizin navigation method'unuz return; // Zaten bağlıysa abone olmayın } videoCall.subscribe(lifecycleOwner, new EventCallback() { @Override public void onEvent(VideoCallEvent event) { runOnUiThread(() -> { // UI güncellemelerinin main thread'de olduğundan emin olun if (event.getType() == CallEventTypes.AgentConnected) { navigateToCallScreen(rtcId); // Sizin navigation method'unuz } else if (event.getType() == CallEventTypes.Error || event.getType() == CallEventTypes.Hangup || event.getType() == CallEventTypes.CallEnded) { String message = event.getDetail(); if (message == null && event.getError() != null) { message = event.getError().getMessage(); } Toast.makeText(QueueActivity.this, "Exiting Queue: " + message, Toast.LENGTH_LONG).show(); navigateBackToMainScreen(); // Sizin navigation method'unuz } }); } }); -
UI: Bir
CircularProgressIndicatorve bekleme metni gösterin. Bir Hangup butonu sağlayın. -
Cleanup: User ekranı erken terk ederse çağrıyı kapatın.
Kotlin (Demo -
DisposableEffect):DisposableEffect(videoCall) { onDispose { // Composable kaldırıldığında bu çalışır // Çağrının hala aktif olup olmadığını kontrol edin (zaten kapatılmamış veya bağlanıp uzaklaşılmamış) if (!videoCall.didHangup && !videoCall.agentConnectedState) { Log.d("QueueScreen", "Disposing QueueScreen, hanging up call ${videoCall.rtcId}") videoCall.hangup() // Queue ekranından erken ayrılırken çağrıyı temizleyin } else { Log.d("QueueScreen", "Disposing QueueScreen, call already handled (hung up or connected).") } } }Java Eşdeğeri (Activity
onDestroyveya FragmentonDestroyViewiçinde):@Override protected void onDestroy() { super.onDestroy(); if (videoCall != null && !videoCall.getDidHangup() && !videoCall.getAgentConnectedState()) { Log.d("QueueActivity", "onDestroy, hanging up call " + videoCall.getRtcId()); // Hangup network calls içeriyorsa potansiyel olarak main thread dışında çalıştırın // onDestroy içinde uzun süren işlemler yapmaktan kaçının new Thread(() -> videoCall.hangup()).start(); // Basit threading örneği } }
Amaç: Devam eden görüntülü görüşme için ana interface. Renderers, kontrolleri ve çağrı state'ini yönetir.
SDK Etkileşimi ve Kod Önemli Noktaları:
**Kotlin (Demo):**
```
val videoCall = remember(rtcId) { AppState.sdkInstance?.getVideoCall(rtcId) }
var webRtcConnection: WebRtcConnection? by remember { mutableStateOf<WebRtcConnection?>(null) }
LaunchedEffect(videoCall) {
if (videoCall == null) { /* Handle error, navigate back */ return@LaunchedEffect }
// Connection'ı alın veya oluşturun
val connection = videoCall.connect() // ConnectParameters gerekirse iletin
if (connection == null) { /* Handle error, navigate back */ return@LaunchedEffect }
webRtcConnection = connection // State'i güncelleyin
// WebRTC setup process'ini başlatın
connection.startRoom(context)
}
```
**Java Eşdeğeri:**
```
// Activity onCreate veya Fragment onViewCreated içinde
String rtcId = getIntent().getStringExtra("RTC_ID");
VideoCall videoCall = MyApp.getSdkInstance().getVideoCall(rtcId);
if (videoCall == null) { /* handle error, finish */ return; }
this.videoCall = videoCall;
WebRtcConnection connection = videoCall.connect(null); // ConnectParameters gerekirse iletin
if (connection == null) { /* handle error, finish */ return; }
this.webRtcConnection = connection;
// WebRTC'yi başlatın - zamanlamayı düşünün, view setup'ını beklemeniz gerekebilir
webRtcConnection.startRoom(this); // Context iletin
```
-
Event Handling & UI State:
videoCall.eventFlow(Kotlin) abone olun veyavideoCall.subscribe(Java) kullanarak UI elements (sessize alma ikonları, hold overlay görünürlüğü vb.) SDK events'e göre güncelleyin. UI state'ini member variables veyaViewModeliçinde saklayın. -
Çağrı Kontrolleri:
Buttontıklamalarını SDK method'larına bağlayın.Kotlin (Demo):
onToggleAudioMuteClick = { webRtcConnection?.setAudioEnabled(!audioState) } onSwitchCameraClick = { webRtcConnection?.switchCamera() } onHangupClick = { /* Show dialog */ videoCall?.hangup() }Java Eşdeğeri:
// Sessize alma butonu için OnClickListener içinde boolean currentAudioState = /* mevcut state'i alın, örn. boolean member'dan */; if (webRtcConnection != null) { webRtcConnection.setAudioEnabled(!currentAudioState); // AudioMuted/Unmuted events'e göre buton ikonunu ve currentAudioState variable'ını güncelleyin } // Kamera değiştirme butonu için OnClickListener içinde if (webRtcConnection != null) { webRtcConnection.switchCamera(); } // Kapatma butonu için OnClickListener içinde new AlertDialog.Builder(this) .setTitle("Confirm Hangup") // ... mesaj, butonlar ... .setPositiveButton("Hangup", (dialog, which) -> { if (videoCall != null) { new Thread(() -> videoCall.hangup()).start(); // Hangup'ı main thread dışında yapın // UI güncellemesi/navigation genellikle CallEnded event'i tarafından yönetilir } }) .show(); -
Video Rendering: Bu, Compose ve geleneksel Views arasında önemli ölçüde farklılık gösterir.
-
Compose:
SDK_Kullanimi.md'de detaylandırıldığı veVideoRenderercomposable'ında gösterildiği gibi lifecycle management içinfactory,updateveonReleaseileAndroidViewkullanır. -
Java/XML:
- XML layout'unuza
<org.webrtc.SurfaceViewRenderer ... />ekleyin. - Renderers referanslarını alın (örn. ViewBinding kullanarak).
- Initialization:
onCreateveyaonViewCreatediçinde,surfaceViewRenderer.init(webRtcConnection.getEglBase().getEglBaseContext(), null)çağırın. - Attachment: Connection hazır olduğunda (örn.
WebRtcConnectedsonrasında veya view kullanılabilir olduğunda)webRtcConnection.attachLocalView(...)vewebRtcConnection.attachRemoteView(...)çağırın. - Detachment & Release: Kritik olarak,
onDestroyveyaonDestroyViewiçinde, hem local hem de remote renderers içinwebRtcConnection.detachLocalView(...),webRtcConnection.detachRemoteView(...)vesurfaceViewRenderer.release()çağırın. Bunu doğru yapmamak leak'lere ve crash'lere neden olur.
- XML layout'unuza
-
-
Immersive Mode:
onResume/onPauseveya ilgili lifecycle events'de system bars gizlemek/göstermek içinWindowInsetsControllerCompatkullanın. -
Cleanup:
onDestroyiçinde, çağrı hala aktifsevideoCall.hangup()çağrıldığından emin olun. Yukarıda belirtildiği gibi renderers release edin.videoCall.subscribe'ınLifecycleOwner'ı tarafından örtük olarak yönetilen event subscriptions durdurulur.Kotlin (Demo -
DisposableEffect):DisposableEffect(Unit) { onDispose { Log.d("CallScreen", "Disposing CallScreen Scope...") // Yalnızca çağrı zaten doğal olarak sona ermediyse kapatın if (videoCall?.didHangup == false) { // SDK flag'ini kontrol edin (varsa) videoCall.hangup() Log.d("CallScreen", "Called hangup for ${videoCall.rtcId} on dispose") } // System bars geri yükleyin if (window != null && !isInPreview) { WindowCompat.getInsetsController(window, view) ?.apply { show(WindowInsetsCompat.Type.systemBars()) } } } }
Demo uygulama, ECV SDK'yı kullanmak için pratik, ancak basitleştirilmiş bir yapı sunar. Anahtar entegrasyon noktaları şunları içerir:
Events ve State Yönetimi
Giriş
ECV Android SDK'sı eşzamansız olarak çalışır. State değişikliklerini, hataları ve diğer önemli olayları uygulamanıza iletmek için event-driven bir mimari kullanır. Bu events anlamak, duyarlı ve sağlam bir UI oluşturmak için kritik öneme sahiptir.
Bu kılavuz, SDK events yapısını detaylandırır, anahtar event types listeler ve ilgili state properties bahseder.
Events Abone Olma
Bir VideoCall instance'ındaki events dinlersiniz. SDK iki ana mekanizma sunar:
Event Yapısı (VideoCallEvent)
Tüm SDK events, group.ccr.ecv.sdk.videoCall.events.VideoCallEvent data class'ının instance'ları olarak teslim edilir.
Anahtar Fields:
İşte yaygın event types bir dökümü, tipik bir çağrının phase'ine göre gruplandırılmıştır:
- **Ne zaman:** `sdkInstance.createCall()` backend'den benzersiz bir çağrı tanımlayıcısı başarıyla aldıktan sonra tetiklenir.
- **Data:** `event.detail`, `rtcId`'yi (String) içerir.
- **Yanıt:** `rtcId`'yi saklayın. Daha sonra `VideoCall` instance'ını almak ve dialing'i başlatmak (`call.start()`) için buna ihtiyacınız vardır.
-
DialSuccess- Ne zaman:
call.start()backend'e dial request'ini başarıyla gönderdikten ve onay aldıktan sonra (genellikle bir Interaction ID içerir) tetiklenir. - Data:
event.detail, Interaction ID'yi içerebilir.event.objData, ham JSON response'u tutabilir. - Yanıt: Genellikle bekleme/queue state'ine geçebileceğinizi gösterir.
- Ne zaman:
-
Error(Init/Dial Sırasında)- Ne zaman:
rtcIdalma başarısız olursa,call.start()parameters geçersizse, backend dial request'ini reddederse veya network sorunları oluşursa meydana gelebilir. - Data:
event.error,ECVErroriçerir.event.detaildaha fazla context sağlayabilir.event.objData, bir HTTP response tutabilir. SpesifikErrorTypesiçinerror.errorkontrol edin (CALL_NOT_INITIALIZED,DIAL_FAILEDgibi). - Yanıt: User'a bir hata mesajı gösterin. Daha fazla çağrı ilerlemesini önleyin. Kullanılamaz durumdaysa
VideoCallinstance'ını potansiyel olarak temizleyin.
- Ne zaman:
- **Ne zaman:** Başarılı bir dial sonrasında, arayanın şimdi bir agent beklediğini belirtmek için tetiklenir.
- **Data:** Genellikle belirli bir data gerekmez.
- **Yanıt:** Bir bekleme UI'si gösterin (`QueueScreen` gibi).
-
AgentConnected-
Ne zaman: Bir agent çağrıyı kabul ettiğinde tetiklenir. Bu, peer-to-peer media bağlantısını kurma tetiğidir.
-
Data: Spesifik bir şey yok.
-
Yanıt: WebRTC bağlantısını başlatın:
WebRtcConnectionalmak içinvideoCall.connect()çağırın.- WebRTC setup'ını başlatmak için
webRtcConnection.startRoom(context)çağırın. - Ana çağrı ekranına (
CallScreen) gidin.
-
-
Error(Queue Sırasında)- Ne zaman: Queue timeouts (
QUEUE_SOCKET_TIMEOUT), queue mekanizmasıyla ilgili network sorunları (QUEUE_SOCKET_ERROR,QUEUE_SOCKET_CALL_DISCONNECTED) veya backend sorunları (WAIT_FOR_ROOM_FAILED, polling kullanılıyorsa) nedeniyle oluşabilir. - Data:
event.error,ECVErroriçerir. - Yanıt: User'a çağrının bağlanamadığını bildirin. Queue ekranından geri gidin. SDK otomatik olarak
CallEndedtetiklemezse cleanup sağlamak içinvideoCall.hangup()çağırın.
- Ne zaman: Queue timeouts (
-
Hangup/CallEnded(Queue Sırasında)- Ne zaman: User açıkça kapatırsa (
videoCall.hangup()) veya çağrı bir agent bağlanmadan önce backend tarafından sonlandırılırsa. - Data:
event.detailbir neden içerebilir ("LOCAL_HANGUP", vb.). - Yanıt: Queue ekranından geri gidin. UI'nin temizlendiğinden emin olun.
- Ne zaman: User açıkça kapatırsa (
Bu events genellikle WebRtcConnection tarafından dahili olarak ele alınır ancak debug veya gelişmiş kullanım durumları için gözlemlenebilir. Başarıyı işaret eden anahtar event WebRtcConnected'dır.
- **Ne zaman:** WebRTC PeerConnection (ICE ve DTLS) tamamen kurulduğunda ve media akışı başlayabildiğinde.
- **Data:** Spesifik bir şey yok.
- **Yanıt:** Genellikle media path'inin hazır olduğunu bilmek için bir tetikleyici olarak kullanılır. `CallScreen`'deki initial loading göstergelerini gizleyebilirsiniz.
TextChannelConnected: Text komutları (hold, flash vb.) için kullanılan özel data channel açık.event.objData,DataChannel'ı tutar.WebRtcDisconnected/WebRtcFailed: WebRTC bağlantısı beklenmedik bir şekilde düştü veya başarısız oldu. Genellikle kısa süre sonra birCallEndedevent'ine yol açar.
- **Yanıt:** Uzak `SurfaceViewRenderer`'ın attach edildiğinden emin olun.
-
AudioMuted/AudioUnmuted: Yerel ses gönderme state'i değişti (setAudioEnabledtarafından tetiklenir).- Yanıt: Mute butonu UI state'ini güncelleyin.
-
VideoMuted/VideoUnmuted: Yerel video gönderme state'i değişti (setVideoEnabledtarafından tetiklenir).- Yanıt: Video mute butonu UI state'ini güncelleyin. Potansiyel olarak yerel video preview overlay'ini gösterin/gizleyin.
-
RemoteAudioMuted/RemoteAudioUnmuted: Uzak katılımcı sesini sessize aldı/açtı.- Yanıt: İsteğe bağlı olarak uzak tarafın sessizde olduğunu gösteren bir indicator görüntüleyin.
-
RemoteVideoMuted/RemoteVideoUnmuted: Uzak katılımcı videosunu sessize aldı/açtı.- Yanıt: Uzak video renderer üzerinde bir overlay veya indicator gösterin/gizleyin.
-
StartHold/EndHold: Çağrı agent tarafından hold'a alındı veya hold'dan çıkarıldı (genellikle text data channel aracılığıyla tetiklenir).- Yanıt: "Call on Hold" overlay'ini gösterin/gizleyin. SDK (
autoHandleHoldSideEffectstrue ise) yanıt olarak yerel medyayı otomatik olarak sessize alabilir/açabilir.
- Yanıt: "Call on Hold" overlay'ini gösterin/gizleyin. SDK (
-
TakePhotoOn/TakePhotoOff: Agent tarafından başlatılan fotoğraf yakalama feature'ları ile ilgili signals.- Yanıt: Gerekirse UI'yi ayarlayın (örn. potansiyel olarak yerel videoyu fullscreen zorlayın). SDK (
autoHandleCameraSwitchtrue ise) kameraları otomatik olarak değiştirebilir.
- Yanıt: Gerekirse UI'yi ayarlayın (örn. potansiyel olarak yerel videoyu fullscreen zorlayın). SDK (
-
TextChannelMessage: Text data channel üzerinden alınan bir komut veya mesaj.- Data:
event.detail, ham mesaj string'ini içerir (örn. "FLASH_ON", "STATUS|VIDEO|MUTED|rtcId"). - Yanıt: SDK, birçok standart mesajı dahili olarak ele alır (HOLD, PICKUP, MUTE durumları, FLASH komutları gibi). Implementation'ınıza özgü custom mesajlara tepki verebilirsiniz.
- Data:
- **Ne zaman:** Yerel olarak `videoCall.hangup()` çağrıldığında hemen tetiklenir.
- **Data:** Spesifik bir şey yok.
- **Yanıt:** Backend çağrının sona erdiğini onaylamadan *önce* UI cleanup'ını başlatmak için kullanılabilir. Unutmayın ki `CallEnded` kesin bitiş event'idir.
-
CallEnded-
Ne zaman: Çağrı session'ının tamamen sona erdiğini gösteren son event; yerel olarak (
hangup()tamamlanması), uzaktan (agent/peer kapatır) veya kurtarılamaz bir error nedeniyle. -
Data:
event.detailgenellikle bir neden içerir (örn. "LOCAL_HANGUP", "REMOTE_HANGUP", "PEER_DISCONNECTED", "CONNECTION_TIMEOUT", error açıklaması). -
Yanıt: Bu, aşağıdakileri yapmak için kesin sinyaldir:
- Tüm çağrıyla ilgili UI'yi (renderers, kontroller) temizleyin.
- Çağrı ekranından uzaklaşın.
- Belirli
VideoCall'a bağlı tüm resources serbest bırakın. SDK tipik olarak bu event'ten sonraVideoCallinstance'ına olan dahili reference'ını kaldırır.
-
Events değişiklikleri bildirirken, bazı mevcut state properties de kontrol edebilirsiniz:
- `rtcId: String`: Benzersiz çağrı tanımlayıcısı (`GotRtcId` sonrası kullanılabilir).
- `agentConnectedState: Boolean`: `AgentConnected` event'i gerçekleştiyse true.
- `didHangup: Boolean`: Yerel olarak `hangup()` çağrıldıysa true.
- `webRtcConnection: WebRtcConnection?`: İlişkili WebRTC connection (`connect()` çağrıldıktan sonra kullanılabilir).
-
WebRtcConnectionÜzerinde:audioEnabled: Boolean: Yerel ses göndermenin mevcut state'i (sonsetAudioEnabledçağrısını yansıtır).videoEnabled: Boolean: Yerel video göndermenin mevcut state'i (sonsetVideoEnabledçağrısını yansıtır).isClosing: Boolean: Bu connection için cleanup process'i başladıysa true.
Özelleştirme Kılavuzu
Giriş
Sağlanan Demo Uygulama (ecv175_android), ECV SDK'sının temel işlevlerini sergiler. Ancak, production uygulamanız şüphesiz farklı UI/UX gereksinimlerine, configuration method'larına ve data ihtiyaçlarına sahip olacaktır. Bu kılavuz, SDK'yı ve demo kavramlarını özel uygulamanıza uyarlama konusunda tavsiyeler sunar.
1. UI Implementation
Demo Jetpack Compose kullanır, ancak SDK'nın kendisi UI-agnostic'tir. Şunlarla oluşturulmuş uygulamalara entegre edebilirsiniz:
- **Permissions:** Çağrıyı başlatmadan önce Kamera, Mikrofon (ve S+ için Bluetooth Connect) permissions istemeli ve yönetmelisiniz.
- **Video Renderers:** Videoyu görüntülemek için layout'unuzda `org.webrtc.SurfaceViewRenderer` instance'ları sağlamanız gerekir.
- **Renderer Lifecycle:** **Bu kritiktir.** `SurfaceViewRenderer`'ların lifecycle'ını doğru şekilde yönetmelisiniz:
- `webRtcConnection.eglBase.eglBaseContext` kullanarak initialize edin.
- `webRtcConnection.attachLocalView`/`attachRemoteView` kullanarak yerel/uzak streams attach edin.
- `webRtcConnection.detachLocalView`/`detachRemoteView` kullanarak detach edin.
- **Artık gerekmediğinde (örn.** `onDestroyView`**,** `onDispose`**, Activity** `onDestroy`**)** `surfaceViewRenderer.release()` **kullanarak release edin. Doğru şekilde release etmemek, crash'lerin ve memory leak'lerin yaygın bir nedenidir.** (`SDK_Kullanimi.md` ve `Demo_Uygulama_Rehberi.md`'deki örneklere bakın).
- **Çağrı State Gösterimi:** UI'niz, SDK events'e dayalı olarak mevcut çağrı durumunu (örn. "Connecting...", "Waiting for agent...", "Connected", "On Hold") yansıtmalıdır.
- **Çağrı Kontrolleri:** Aşağıdakiler için UI elements (Buttons, Icons) implement edin:
- Sesi Sessize Alma/Açma (`webRtcConnection.setAudioEnabled(...)`)
- Videoyu Açma/Kapatma (`webRtcConnection.setVideoEnabled(...)`)
- Kamerayı Değiştirme (`webRtcConnection.switchCamera(...)`)
- Kapatma (`videoCall.hangup()`)
- **Event Handling:** UI layer'ınız, state'ini güncellemek, ekranlar arasında gezinmek ve hataları ele almak için SDK events (bkz. `Olaylar_ve_Durum_Yonetimi.md`) dinlemelidir.
Demo'nun EnvironmentSelectorScreen'i, farklı backend environment'larını kolayca test etmek için tasarlanmıştır. Production uygulamanızda muhtemelen bu ekrana ihtiyacınız olmayacaktır.
Production Configuration Stratejileri:
```
// Örnek: Application.onCreate veya DI module içinde
val prodConfig = ECVSdkConfiguration(
serviceUrl = "https://sizin-prod.ecv.example.com",
)
val sdkInstance = ECVSdk("MyAppProdInstance", prodConfig)
```
```
// Örnek: Application onCreate veya DI module içinde
ECVSdkConfiguration prodConfig = new ECVSdkConfiguration(
/* serviceUrl */ "https://sizin-prod.ecv.example.com",
// ... diğer constructor args ...
/* useQueueSocket */ true // Backend'in v1.75+ olduğunu varsayarsak
// ...
);
ECVSdk sdkInstance = new ECVSdk("MyAppProdInstance", prodConfig);
// sdkInstance'ı global olarak saklayın
```
-
Dinamik Configuration:
ECVSdk'yı initialize etmeden önce kendi uygulama backend'inizden configuration ayrıntılarını (serviceUrl,webrtcUrlgibi) dinamik olarak alın. Bu, uygulama güncellemeleri gerektirmeden endpoints merkezi olarak yönetmenizi sağlar.- Uygulamanız config'i server'ınızdan alır.
ECVSdkConfigurationoluşturmak için alınan URL'leri kullanır.ECVSdk'yı initialize eder.
Demo, seçilen environment'a göre çağrı data'sı için dinamik olarak dropdown'lar eklemek üzere EnvironmentCallAttribute kullanır. Bu, demo'nun çoklu environment setup'ına özgüdür.
Uygulamanızda, videoCall.start() çağırmadan hemen önce DialCallParameters'ı ilgili bağlamsal data ile doldurmalısınız.
Örnek:
// User profile data
val userId = "user123"
val accountType = "Premium"
val intent = "Billing Inquiry"
// Hedef queue/skill
val targetQueue = "BillingSupport"
// SDK instance'ını alın ve çağrı oluşturun
val videoCall = sdkInstance.createCall()
// ... GotRtcId bekle ...
// Uygulamaya özgü data ile parameters oluşturun
val dialParams = DialCallParameters(
callerName = "User $userId", // Veya gerçek user adı
attributes = mapOf(
"userId" to userId,
"accountType" to accountType,
"callIntent" to intent
),
acdAttributes = mapOf(
"skill" to "Billing" // Örnek ACD attribute
),
queue = targetQueue, // Hedef queue'yu açıkça ayarlayın
additionalHeaders = mapOf("Authorization" to "Bearer ${getUserAuthToken()}") // Örnek auth
)
// Çağrıyı başlatın
videoCall.start(dialParams)
// Queue/Bekleme ekranına gidin
// User profile data
String userId = "user123";
String accountType = "Premium";
String intent = "Billing Inquiry";
// Hedef queue/skill
String targetQueue = "BillingSupport";
// SDK instance'ını alın ve çağrı oluşturun
VideoCall videoCall = MyApp.getSdkInstance().createCall();
// ... GotRtcId event'ini subscribe kullanarak ele alın ...
// GotRtcId event'inin tetiklendiğini ve dial etmeye hazır olduğunuzu varsayın:
// Uygulamaya özgü data ile parameters oluşturun
Map<String, String> attributes = new HashMap<>();
attributes.put("userId", userId);
attributes.put("accountType", accountType);
attributes.put("callIntent", intent);
Map<String, String> acdAttributes = new HashMap<>();
acdAttributes.put("skill", "Billing");
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + getUserAuthToken()); // Örnek auth
DialCallParameters dialParams = new DialCallParameters(
/* callerName */ "User " + userId, // Veya gerçek user adı
/* attributes */ attributes,
/* acdAttributes */ acdAttributes,
/* queue */ targetQueue, // Hedef queue'yu açıkça ayarlayın
/* customPayload */ new HashMap<>(),
/* additionalHeaders */ headers
);
// Çağrıyı başlatın
videoCall.start(dialParams);
// Queue/Bekleme ekranına gidin
4. Event Handling Logic
Demo, events nasıl abone olunacağını gösterirken, yanıt olarak ne yapacağınız uygulamaya özgüdür. Implementation'ınızın aşağıdakiler için anahtar events doğru şekilde ele aldığından emin olun:
## 5. Gelişmiş Özelleştirme (İsteğe bağlı)Her zaman Android'in permissions isteme için en iyi practice'lerini izleyin:
Aşağıdaki maddeler ECV sistemi üzerinde çalıştırılabilecek ek modüllerin mobil uygulama içerisinde olan etkisini anlatmaktadır. Bu modüller default olarak aktif değildir. Aktif edilmesi için CCR satış temsilciniz ile iletişime geçmeniz gerekmektedir.
7.1 Çağrı Transferi v1.0.18.45+
Bu modül ile aktif devam eden bir çağrının farklı bir agent/kuyruk seçerek yönlendirilmesi sağlanmaktadır. Transfer edilen bir çağrının lifecycle'ı aşağıdaki şekildedir;
- Kaynak çağrı dial edilir. (çağrı oluşturma)
- Kaynak çağrı kuyrukta bekler.
- Kaynak çağrı bağlanır
- Agent transfer başlatır
- Transfer kuyruğa veya doğrudan farklı bir agent'a olabilir.
- Doğrudan agent'a yapılan transfer iptal edilebilir veya agent tarafından reject edilebilir.
- videoCall.eventFlow üzerinden "TransferPending" eventi gelir
- Bu event geldiğinde kullanıcıya arayüzde "Lütfen bekleyin çağrınınız birazdan transfer edilecek." veya benzeri bir mesaj gösterilebilir.
- Bu event sonrasında T anında TransferCalceled eventi gelebilir, cancel eventi geldiğinde gösterilen uyarı gizlenmelidir. Çağrı aynen devam edecektir.
- Agent transfer etmeden önce çağrıyı HOLD statüsüne geçirmiş olabilir, bu nedenle transfer mesajı HOLD arayüzünden daha önde olmalıdır.
- Transfer tamamlandığında eventFlow üzerinden TransferCompleted eventi gelecektir. event.detail içerisinde transfer edilen çağrıya atanan yeni RtcId değeri bulunmaktadır.
- Mevcut çağrı view'i ve bağlı bütün kaynaklar destroy edilir.
- Kullanıcı tekrar kuyruk sayfasına, bu sefer yeni RtcId ile gönderilir.
- Aynı yeni çağrı oluşturulmuş gibi kuyrukta bekler ve agent hazır olduğunda çağrıya AgentConnected eventi ile çağrılır.
Bu senaryoya ait implementasyon örneği demo uygulama içerisinde mevcuttur.
Sorun Giderme Kılavuzu
Bu kılavuz, ECV Android SDK'sını entegre ederken veya kullanırken karşılaşılan yaygın sorunlar için çözümler ve debug adımları sunar.
Yaygın Sorunlar ve Çözümler
Build & Dependency Hataları
- **Neden:** Gradle gerekli `.aar` dosyalarını bulamıyor.
- **Çözüm:**
- **Üç** gerekli `.aar` dosyasının (ECV SDK, `M134-libwebrtc`, `autobahn-android-legacy-20.2.1`) doğrudan projenizin `app/libs` dizinine kopyalandığından emin olun.
- Uygulama düzeyindeki `build.gradle`/`.kts` dosyanızın `repositories { ... }` bloğu içinde `flatDir { dirs 'libs' }` girişini içerdiğini doğrulayın.
- Dependencies bloğunuzdaki `implementation(name: '...', ext: 'aar')` satırlarının `libs` dizinindeki dosya adlarıyla ( `.aar` uzantısı olmadan) tam olarak eşleştiğini onaylayın.
- Bir Gradle Sync yapın (`File > Sync Project with Gradle Files`). `Build > Clean Project` ve `Build > Rebuild Project` deneyin.
-
Hata:
Unresolved reference: lifecycleveyaandroidx.lifecycleile ilgili benzer hatalar- Neden: Gerekli
androidx.lifecycle:lifecycle-runtime-ktxdependency'si uygulamanızın build dosyasında eksik. SDK bunu dahili olarak kullanır ancakapiaracılığıyla expose etmez. - Çözüm:
implementation("androidx.lifecycle:lifecycle-runtime-ktx:VERSION")dependency'sini uygulama düzeyindekibuild.gradle/.ktsdosyanıza ekleyin,VERSIONyerine projenizle uyumlu güncel bir stabil version (örn.2.7.0) yazın. Detaylar içinREADME_tr.md'ye bakın. Gradle'ı senkronize edin.
- Neden: Gerekli
-
Hata: Duplicate Class Found...
-
Neden: Projeniz veya başka bir library, SDK'nın AAR'ları tarafından dahili olarak kullanılan dependency'lerin çakışan version'larını içeriyor (örn. farklı WebRTC veya Autobahn version'ları, potansiyel Kotlin standard library sorunları).
-
Çözüm:
-
Çakışan library'leri tanımlamak için build hata mesajını analiz edin.
-
Transitive dependency'leri exclude etmek veya belirli version'ları force etmek için Gradle'ın dependency resolution stratejilerini kullanın. Örnek (Groovy):
implementation('some.other.library:1.0') { // ECV SDK'nın dependency'leri tarafından sağlanan çakışan bir module exclude et exclude group: 'org.webrtc', module: 'google-webrtc' } -
Projenizin core library'lerin (Kotlin gibi) uyumlu version'larını kullandığından emin olun.
-
-
-
Hata: Manifest Merger Failed (Permissions)
-
Neden: SDK'nın manifest'i, uygulamanızın manifest'i ile çakışan permissions veya features declare edebilir.
-
Çözüm:
- Build output'undaki belirli manifest merger hata mesajını inceleyin.
- Uygulamanızın
AndroidManifest.xml'ininCAMERA,RECORD_AUDIO,INTERNET,MODIFY_AUDIO_SETTINGSve potansiyel olarakBLUETOOTH_CONNECT(API 31+ için) gibi gerekli permissions doğru şekilde declare ettiğinden emin olun. - Gerekirse manifest merger override kurallarını (
tools:overrideLibrary,tools:node="replace") dikkatli kullanın, ancak sonuçlarını anlayın. Genellikle, uygulamanızın gerekli permissions declare etmesi yeterlidir.
-
- **Neden:** Native WebRTC library'leri (`M134-libwebrtc.aar` içindeki `.so` dosyaları) yüklenemedi. Bu genellikle architecture uyuşmazlıkları veya kurulum sorunlarından kaynaklanır.
- **Çözüm:**
- `M134-libwebrtc.aar`'ın doğru şekilde dahil edildiğinden ve Gradle tarafından işlendiğinden emin olun.
- Build'inizde ABI splits kullanıyorsanız, hedef cihaz/emulator için doğru architecture'ın `.so` dosyalarının dahil edildiğinden emin olun.
- Projeyi clean edip rebuild yapın. Mümkünse farklı cihazlarda/emulator'lerde test edin.
- Başka hiçbir library'nin native library yüklemesine müdahale etmediğini doğrulayın.
-
Crash:
NullPointerException-
Neden: Bir SDK object'ine initialize edilmeden önce veya cleanup yapıldıktan sonra erişilmesi.
-
Çözüm:
- SDK Not Initialized:
sdkInstance'a (örn.MyApp.getSdkInstance()) erişmeye çalışmadan önceECVSdk(...)çağrıldığından emin olun. Log'larda initialization hatalarını kontrol edin. getVideoCallNull Döndürüyor:rtcIdgeçersiz olabilir, çağrı zaten sona ermiş olabilir (CallEndedevent'i) veya SDK instance'ı yeniden initialize edilmiş olabilir. Özellikle bir Activity/Fragment'ı resume ederken veya navigate ederkenVideoCallobject'ini kullanmadan öncegetVideoCall(rtcId)'nin dönüş değerini her zaman null için kontrol edin.WebRtcConnectionNull:videoCall.connect()'inAgentConnectedevent'inden sonra vewebRtcConnection'a erişmeden önce başarıyla çağrıldığından emin olun.connect()veyastartRoom()process'i sırasındaki hataları kontrol edin.- Context Null: Gerektiğinde geçerli
Contextiletildiğinden emin olun (örn.startRoom).
- SDK Not Initialized:
-
-
Crash:
SurfaceViewRenderer.release()veya EGL hatalarıyla ilgili-
Neden:
SurfaceViewRenderer'ın yanlış lifecycle management'ı. -
Çözüm:
- Release MUTLAKA çağrılmalı:
surfaceViewRenderer.release()'in uygun lifecycle callback'inde (onReleaseComposeAndroidView'da,onDestroyView/onDestroyFragments/Activities için) çağrıldığından emin olun. - Release Öncesi Detach: Kritik olarak,
release()çağırmadan önce renderer üzerindewebRtcConnection.detachLocalView(...)vewebRtcConnection.detachRemoteView(...)çağırın. - Threading:
init,attach/detachverelease'in doğru thread'den (genellikle main UI thread) çağrıldığından emin olun. - Double Release:
release()'in aynı renderer instance'ında birden çok kez çağrılmadığından emin olun.
- Release MUTLAKA çağrılmalı:
-
-
Crash:
SecurityException(Permission Reddi)-
Neden: User tarafından verilen gerekli runtime permissions olmadan kamerayı veya mikrofonu başlatmaya çalışmak.
-
Çözüm:
videoCall.start()veyawebRtcConnection.startRoom()çağırmadan önce sağlam bir permission request mantığı implement edin.- Çağrı işlevini etkinleştirmeden önce permission durumunu kontrol edin.
- User permissions reddettiği durumları zarifçe ele alın. Bir örnek için demo'nun
MainScreen.kt'sine bakın.
-
- **Debugging:**
- **Network:** Bu en yaygın nedendir. Cihazdaki network bandwidth, latency ve packet loss kontrol edin (örn. hız testi uygulamaları, ping kullanarak). Farklı network'lerde test edin (Wi-Fi vs Cellular).
- **Cihaz Performance:** Eski veya düşük seviye cihazlar, özellikle daha yüksek resolutions'da encoding/decoding ile mücadele edebilir.
- `ConnectParameters`**:** Bandwidth sınırlıysa `videoWidth`/`videoHeight`/`videoFps` düşürmeyi veya bir `videoMaxBitrate` ayarlamayı düşünün. Mümkünse `videoCodecHwAcceleration`'ın etkinleştirildiğinden emin olun.
- **WebRTC Stats:** Ayrıntılı WebRTC statistics toplamak için `peerConnectionClient.enableStatsEvents` kullanın (ancak bunları yorumlamak uzmanlık gerektirir).
- **Debugging:**
- `webRtcConnection.switchCamera()` çağrıldığından emin olun.
- Switch denemesi sırasındaki hatalar için log'ları kontrol edin.
- Cihazın gerçekten birden fazla kamerası (ön/arka) olduğunu doğrulayın.
-
Hold/Pickup Otomatik Olarak Sessize Almıyor/Açmıyor:
-
Debugging:
ConnectParametersiçindeautoHandleHoldSideEffects = trueolduğundan emin olun (bu default'tur).StartHold/EndHoldevents doğru şekilde alınıp alınmadığını kontrol edin.- "HOLD|" veya "PICKUP|" ile ilgili
TextChannelMessageevents alınıp alınmadığını kontrol edin. Text channel'ın bağlı olduğunu doğrulayın (TextChannelConnectedevent'i).
-
Bu adımları izledikten sonra sorunlarla karşılaşmaya devam ederseniz, lütfen desteğe başvururken aşağıdaki bilgileri hazırlayın: