Skip to main content

ECV Görüntülü Görüşme Android SDK

image-20240102-113213.pngimage-20240102-113321.png

ECV Görüntülü Görüşme Android SDK

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:

  1. SDK: Önceden compile edilmiş bir Android Library (.aar dosyası) olarak sağlanır. Bunu projenize bir dependency olarak ekleyeceksiniz.

  2. Demo Uygulama: SDK feature'larının nasıl kullanılacağını gösteren bir tanıtım uygulamasının (ecv175_android) tam kaynak kodu. Bu demo, production'a hazır bir UI kodu değil, bir referans ve örnek olarak tasarlanmıştır.

Temel Özellikler

  • ECV backend services (v1.75+) bağlantısı kurar.

  • WebRTC kurulumunu ve peer-to-peer bağlantıyı yönetir.

  • Yerel ve uzak video/ses media streams yönetir.

  • Ses ve videoyu sessize alma/açma için kontroller sağlar.

  • Kamera değiştirmeyi destekler.

  • Çağrı lifecycle Events (dialing, queuing, connected, hold, ended) yönetir.

  • Yapılandırılabilir environment'lar ve çağrı parameters.

Başlarken

Ön Koşullar

  • Android Studio (En Son Kararlı Sürüm Önerilir)

  • Temel Android geliştirme bilgisi (Kotlin veya Java).

  • Minimum Android SDK Sürümü: 21 (SDK'nızın minSdk değeriyle eşleştiğini onaylayın)

  • Hedef Android SDK Sürümü: 35 (SDK'nızın targetSdk değeriyle eşleştiğini onaylayın)

1. Gerekli Library'leri Ekleme

Uygulamanızın ECV SDK library'sini ve gerekli dependency'lerini içermesi gerekir: WebRTC ve Autobahn. Bunları .aar dosyaları olarak alacaksınız.

// 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ı...
}
  1. 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

  2. Üç .aar dosyasını da projenizin libs dizinine kopyalayın (app modülü altında yoksa oluşturun).

  3. Uygulama düzeyindeki build.gradle (veya build.gradle.kts) dosyanızı açın.

  4. libs dizinini, eğer zaten ekli değilse, repositories kısmına ekleyin:

    // build.gradle (Groovy)
    repositories {
        google()
        mavenCentral()
        flatDir {
            dirs 'libs' // Bu satırı ekleyin
        }
        // Gerekirse diğer özel repoları ekleyin
    }
    
    // build.gradle.kts (Kotlin)
    repositories {
        google()
        mavenCentral()
        flatDir {
            dirs("libs") // Bu satırı ekleyin
        }
        // Gerekirse diğer özel repoları ekleyin
    }
    
  5. 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)
    }
    
  6. Lifecycle Runtime Dependency Ekleme (Gerekirse):
    ECV SDK, dahili olarak androidx.lifecycle:lifecycle-runtime-ktx library'sini (implementation dependency) kullanır. Bu bir api dependency 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.kts dosyanızda bulunduğundan emin olun:


    Not: Version catalog (libs.) kullanmıyorsanız, libs.androidx.lifecycle.runtime.ktx ifadesini "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).

  7. Gradle projenizi senkronize edin (File > Sync Project with Gradle Files).

2. Demo Uygulamayı Keşfetme

Dahil edilen demo uygulama (ecv175_android), SDK entegrasyonunun pratik bir örneğini sunar.

  1. ecv175_android projesini Android Studio'da açın.

  2. Demo uygulamayı bir emulator'de veya fiziksel cihazda build edip çalıştırın.

  3. SDK component'lerinin nasıl initialize edildiğini ve kullanıldığını anlamak için kaynak kodunu, özellikle ui package'ındaki dosyaları (EnvironmentSelectorScreen.kt, MainScreen.kt, QueueScreen.kt, CallScreen.kt) ve AppState.kt (veya eşdeğeri) object'ini inceleyin.

3. Temel SDK Başlatma

// 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

  • ECVSdk: SDK ile etkileşim için ana giriş noktası. Çağrı oluşturmak ve genel ayarlara/modules erişmek için kullanılır.

  • ECVSdkConfiguration: SDK için backend URL'lerini ve bağlantı parameters tanımlar.

  • VideoCall: Tek bir görüntülü görüşme session'ını temsil eder. Çağrı state'ini, Events'i ve bağlantı ayrıntılarını yönetir.

  • WebRtcConnection: Temeldeki WebRTC peer connection'ını, media streams ve Renderers yönetir. VideoCall.connect() aracılığıyla erişilir.

  • Events: SDK, çağrı state değişikliklerini, hataları ve medya olaylarını (CallEventTypes) uygulamanıza bildirmek için bir flow/callback sistemi (VideoCall.eventFlow / VideoCall.subscribe) kullanır.

  • Renderers (SurfaceViewRenderer): Yerel ve uzak videoyu görüntülemek için kullanılan standart WebRTC view'ları. Lifecycle'larını yönetmeniz ve WebRtcConnection kullanarak attach/detach etmeniz gerekir.

Demo Uygulama Akışı

Demo uygulama şu temel flow'u izler:

  1. EnvironmentSelectorScreen: Backend bağlantı configurations (Service URL'si vb.) seçmeye veya tanımlamaya olanak tanır. Bu öncelikle tanıtım amaçlıdır; uygulamanız muhtemelen sabit bir configuration sahip olacak veya dinamik olarak alacaktır.

  2. MainScreen: Gerekli çağrı bilgilerini (örn. arayan adı, attributes) toplar, permissions (Kamera, Mikrofon) ister ve sdk.createCall() ve call.start() kullanarak çağrıyı başlatır.

  3. QueueScreen: Çağrı bir agent'a bağlanırken bekleme durumunu gösterir. AgentConnected veya Error/Hangup Events dinler.

  4. CallScreen: Ana görüntülü görüşme interface'i. Yerel ve uzak video için SurfaceViewRenderer'ları yönetir, çağrı kontrollerini (sessize alma, kamera değiştirme, kapatma) gösterir ve UI'yi güncellemek için SDK Events tepki verir.

Kullanım Kılavuzu

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:

  • config: Initialization sırasında iletilen ECVSdkConfiguration.

  • logger: Dahili bir logger instance'ı. Log'lar genellikle Android'in Logcat'inde görüntülenebilir.

  • modules: SDK modules erişim sağlar (şu anda event subscription'a odaklanmıştır).

2. Çağrıları Yönetme (VideoCall)

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:

  1. Initialization: createCall() çağrıldığında, SDK backend'den eşzamansız olarak bir rtcId alır. GotRtcId event'ini dinlemelisiniz. rtcId, çağrıyı tanımlamak için gereklidir.

    • videoCall.rtcId: Benzersiz ID'ye erişin (GotRtcId event'inden sonra kullanılabilir).

  2. 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): DialCallParameters için Yapilandirma.mdye bakın.

    • DialSuccess veya Error events dinleyin.

    • OnQueue event'i, çağrının bir agent beklediğini gösterir.

  3. Agent Bağlantısı:

    • AgentConnected event'i, bir agent çağrıyı kabul ettiğinde tetiklenir.

    • videoCall.agentConnectedState: Agent'ın bağlı olup olmadığını gösteren Boolean flag'i.

  4. WebRTC Bağlantısı (connect): AgentConnected sonrasında, peer-to-peer media bağlantısını kurarsınız.

    • videoCall.connect(parameters: ConnectParameters? = null): WebRtcConnection: WebRtcConnection object'ini oluşturur veya alır. ConnectParameters için Yapilandirma.mdye bakın.

  5. 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.

  6. Medya Kontrolü: WebRtcConnection instance'ındaki method'ları kullanın (sonraki bölüme bakın).

  7. Ç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.

    • Hangup event'ini ( hangup() çağrıldığında tetiklenir) veya CallEnded event'ini (genellikle uzak taraf veya errors tarafından tetiklenir) dinleyin.

Mevcut Bir Çağrıyı Bulma:

Ç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:

  • startRoom(context: Context): (Yukarıda belirtildiği gibi) AgentConnected sonrasında WebRTC bağlantı setup'ını başlatır. Context gerektirir.

  • eglBase: EglBase: SurfaceViewRenderer'ları initialize etmek için gereken EGL context'ini sağlar.

  • attachLocalView(renderer: SurfaceViewRenderer): Yerel kamera feed'ini görüntülemek için bir renderer ekler.

  • attachRemoteView(renderer: SurfaceViewRenderer): Uzak katılımcının video feed'ini görüntülemek için bir renderer ekler.

  • detachLocalView(renderer: SurfaceViewRenderer): Yerel feed'den bir renderer çıkarır.

  • detachRemoteView(renderer: SurfaceViewRenderer): Uzak feed'den bir renderer çıkarır.

  • setAudioEnabled(state: Boolean): Yerel ses çıkış stream'ini sessize alır veya açar. AudioMuted/AudioUnmuted events tetikler.

  • setVideoEnabled(state: Boolean): Yerel video stream'ini göndermeyi durdurur veya başlatır. VideoMuted/VideoUnmuted events tetikler.

  • switchCamera(targetCamera: String? = null): Ön ("f") ve arka ("b") kameralar arasında geçiş yapar. targetCamera null ise, geçiş yapar.

Renderer Lifecycle Management (Kritik):

SurfaceViewRenderer, özellikle AndroidView kullanan Jetpack Compose'da dikkatli lifecycle management gerektirir.

  1. Initialization: AndroidView'ın factory lambda'sı çalıştığında, SurfaceViewRenderer'ı oluşturun ve webRtcConnection.eglBase.eglBaseContext kullanarak initialize edin.

  2. Attachment: Renderer'ı factory veya update lambda'sı içinde webRtcConnection.attachLocalView() veya webRtcConnection.attachRemoteView() kullanarak yerel veya uzak stream'e attach edin.

  3. Detachment: Hedef stream değişirse (örn. PiP'de yerel/uzak arasında geçiş yaparken) update lambda'sında renderer'ı detach edin.

  4. Release: Kritik olarak, resources serbest bırakmak ve leak'leri önlemek için AndroidView'ın onRelease lambda'sında renderer'ı detach edin (detachLocalView/detachRemoteView) ve surfaceViewRenderer.release() çağırın.

Somut bir örnek için demo uygulamadaki CallScreen.kt -> VideoRenderer composable'ına bakın.

// 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:

  • Kotlin (Flow): Coroutines ve collect kullanarak videoCall.eventFlow üzerinde toplayın. Bu, LaunchedEffect veya lifecycleScope.launch/repeatOnLifecycle ile kullanıldığında lifecycle-aware'dir.

    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): subscribe method'unu bir LifecycleOwner ve bir EventCallback ile 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() { ... });
    

Anahtar Event Types (CallEventTypes):

Daha kapsamlı bir liste için Olaylar_ve_Durum_Yonetimi.md dosyasına bakın, ancak bazı kritik olanlar şunlardır:

  • GotRtcId: SDK, benzersiz çağrı tanımlayıcısını aldı.

  • DialSuccess: Backend, çağrı isteğini onayladı.

  • Error: Bir hata oluştu (event.error ve event.detail kontrol edin).

  • OnQueue: Arayan bir agent bekliyor.

  • AgentConnected: Agent katıldı, media kurmaya hazır.

  • WebRtcConnected: Peer-to-peer media bağlantısı tamamen kuruldu.

  • AudioMuted/AudioUnmuted: Yerel ses gönderme state'i değişti.

  • VideoMuted/VideoUnmuted: Yerel video gönderme state'i değişti.

  • RemoteAudioMuted/RemoteAudioUnmuted: Uzak katılımcının ses state'i değişti.

  • RemoteVideoMuted/RemoteVideoUnmuted: Uzak katılımcının video state'i değişti.

  • StartHold/EndHold: Çağrı bekletme durumu değişti.

  • Hangup: Yerel kapatma başlatıldı.

  • CallEnded: Çağrı session'ı sona erdi (yerel veya uzak).

5. Error Handling

Hatalar genellikle Error event'i (CallEventTypes.Error) aracılığıyla bildirilir. VideoCallEvent object'i şunları içerir:

  • event.error: Bir ErrorTypes enum ve bir mesaj içeren bir ECVError object'i.

  • event.detail: Varsa, hatayla ilgili ek string ayrıntıları.

  • event.objData: Bazen ilgili data içerebilir (örn. HTTP response).

Her zaman Error events kontrol edin ve uygun şekilde ele alın (örn. user'a bir mesaj gösterin, çağrıyı temizleyin, geri gidin).

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:

  • serviceUrl: String (Gerekli): Ana ECV backend API services (örn. rtcId alma, dialing, hangup bildirimleri) için temel URL.

    • Örnek: "https://sirketiniz.ecv.backend.com"

  • webrtcUrl: String (İsteğe bağlı, default serviceUrl): Özellikle WebRTC signaling ile ilgili HTTP requests ( /join endpoint'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"

  • signalingUrl: String (İsteğe bağlı, default serviceUrl): WebSocket signaling server için temel URL. SDK, http/https yerine ws/wss kullanacaktır. WebSocket server'ınız ayrı olarak host ediliyorsa, burada belirtin.

    • Örnek: "https://sirketiniz.ecv.websocket.com"

  • useQueueSocket: Boolean (İsteğe bağlı, default true): Queue state güncellemelerini ( AgentConnected gibi) 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.

Örnek Kullanım:

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:

  • callerName: String (İsteğe bağlı, default ""): Arayan için bir görünen ad, potansiyel olarak agent'a gösterilir.

  • attributes: Map<String, String> (İsteğe bağlı, default emptyMap()): Genel çağrı attributes veya customer data temsil eden key-value çiftlerinin bir map'i. Bu bilgi, dial process sırasında backend'e gönderilir ve routing, screen pops veya context için kullanılabilir.

    • Örnek: mapOf("customerId" to "12345", "product" to "Premium Account")

  • acdAttributes: Map<String, String> (İsteğe bağlı, default emptyMap()): Backend'deki ACD (Automatic Call Distributor) routing mantığı için tasarlanmış özel attributes.

    • Örnek: mapOf("skill" to "Billing", "language" to "en-US")

  • queue: String? (İsteğe bağlı, default null): Çağrıyı yönlendirmek için belirli queue veya workgroup adı. null veya boşsa, backend'deki default routing kuralları geçerlidir.

    • Örnek: "SalesQueue"

  • customPayload: Map<String, String> (İsteğe bağlı, default emptyMap()): Özel backend entegrasyonunuzun gerektirdiği diğer custom data göndermek için esnek bir map.

  • additionalHeaders: Map<String, String> (İsteğe bağlı, default emptyMap()): /api/dialex request'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 Kullanım:

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):

  • Connection & Signaling:

    • 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çin false olarak ayarlayın.

    • disableVideo: Boolean = false: Gerekirse video gönderme/alma işlemini tamamen devre dışı bırakmak için üst düzey flag (genellikle videoCallEnabled yeterlidir).

    • 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 = false gerektirir).

    • 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çin DataChannelConfig class'ına bakın.

Örnek Kullanım:

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.

  • Environment Data Class: Bir name, SerializableECVSdkConfiguration ve bir isDefault flag'i tutar.

  • SerializableECVSdkConfiguration: Kaydetme/yükleme için kullanılan, ECVSdkConfiguration'ı yansıtan serializable bir version.

  • Default Environments: loadInitialEnvironments() içinde tanımlanır, UI'den silinemez. Bir default'u düzenlemek yeni bir custom environment oluşturur.

  • Custom Environments: User tarafından "+" butonuyla veya bir default'u düzenleyerek eklenir. Bunlar silinebilir.

  • Storage: Custom environment'lar Android DataStore (saveEnvironments/loadEnvironments) kullanılarak kaydedilir.

  • Dinamik Çağrı Attributes (EnvironmentCallAttribute): Demo, environment başına attributes (örn. farklı queues) tanımlamaya olanak tanır; bunlar MainScreen'de dropdown olarak sunulur ve DialCallParameters'a dahil edilir.

Production uygulamanızda muhtemelen şunları yapacaksınız:

  1. Sabit bir ECVSdkConfiguration'a sahip olacaksınız,

  2. Veya, ECVSdk'yı initialize etmeden önce kendi backend service'inizden configuration ayrıntılarını dinamik olarak alacaksınız.

  3. customerId veya hedef queue gibi çağrıya özgü data'yı, demonun environment attribute sistemi yerine uygulamanızın context'ine göre doğrudan DialCallParameters'a ileteceksiniz.

Demo Uygulama Rehberi

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:

  1. Global State Management (AppState.kt)

  2. Environment Seçimi (EnvironmentSelectorScreen.kt)

  3. Çağrı Başlatma (MainScreen.kt)

  4. Bekleme Queue (QueueScreen.kt)

  5. Aktif Çağrı (CallScreen.kt)

Ön Koşullar

  • SDK'yı ve dependency'lerini (.aar dosyaları, lifecycle-runtime-ktx) environment'ınızda README_tr.md'de açıklandığı gibi başarıyla kurmuş olun.

  • ecv175_android demo projesini Android Studio'da build edip çalıştırabiliyor olun.

  • Kotlin/Compose veya Java/XML Android geliştirmeye aşina olun.

1. Global State Management (AppState.kt)

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:

  • Merkezi Instance: sdkInstance'ı global olarak saklamak, uygulama genelinde erişilebilir olmasını sağlar. Mimarınıza uygun bir pattern seçin (Application class, Singleton, DI).

  • Initialization: SDK'yı, genellikle uygulama başlangıcında, uygun ECVSdkConfiguration ile bir kez initialize edin.

2. Environment Seçimi (EnvironmentSelectorScreen.kt)

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:

  • Configuration: SerializableECVSdkConfiguration içeren Environment object'lerini yönetir. Bu, ECVSdk instance'ını initialize etmeden önce (muhtemelen AppState.initializeSdk aracılığıyla) SDK'nın ECVSdkConfiguration'ına dönüştürülür.

  • Kaydetme/Yükleme: Custom configurations kalıcı kılmak için DataStore (loadEnvironments, saveEnvironments) kullanır.

  • Initialization: Bir environment seçmek, seçilen configuration ile AppState.sdkInstance'ın (yeniden) initialize edilmesini tetikler.

Kod Önemli Noktaları:

  • loadInitialEnvironments(): Hardcoded default environment'lar sağlar.

  • LaunchedEffect: Ekran başladığında initial ve saved environment'ları yükler.

  • onEnvironmentSelected: Bir öğe tıklandığında tetiklenen callback. Demo'da bu muhtemelen AppState'i güncelleyip uzaklaşmaya yol açar.

  • EnvironmentDialog: Environment ayrıntılarını eklemek/düzenlemek için Composable.

Production Uygunluğu: Düşük. Uygulamanız tipik olarak sabit bir configuration sahip olacak veya kendi backend'inizden dinamik olarak alacaktır, son user'lara böyle bir seçim UI'si sunmayacaktır. Dinamik attribute seçimi (EnvironmentCallAttribute) de demo'ya özgüdür; çağrı data'sını doğrudan iletirsiniz.

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ı:

  1. Permissions:

    • 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 ...
    
  2. Data Toplama:

    • callerName için OutlinedTextField.

    • AppState.currentConfig?.callerAttributeSelections boş değilse SimpleSelectDropdown (bir demo helper) kullanılır, callerAttributes map'ini doldurur.

  3. Çağrıyı Başlatma: ("START" butonunun onClick'i içinde)

    • Çağrı Oluşturma: SDK'dan bir VideoCall instance'ı 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) GotRtcId Bekleme: Demo, createCall'dan kısa süre sonra rtcId'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")
      }
      
    • DialCallParameters Oluş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);
    

4. Bekleme Queue (QueueScreen.kt / QueueActivity veya QueueFragment)

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ı:

  1. VideoCall Instance'ını Alma: Navigation sırasında iletilen rtcId ile ilişkili VideoCall object'ini alın. Çağrının artık mevcut olmayabileceği durumu ele alın.

    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;
    
  2. Event Handling: Belirli videoCall iç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 (subscribe kullanarak):

    // 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
                }
            });
        }
    });
    
  3. UI: Bir CircularProgressIndicator ve bekleme metni gösterin. Bir Hangup butonu sağlayın.

  4. 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 onDestroy veya Fragment onDestroyView iç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
        }
    }
    

5. Aktif Çağrı (CallScreen.kt / CallActivity veya CallFragment)

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ı:

  1. VideoCall ve WebRtcConnection Alma: QueueScreen'e benzer şekilde, rtcId aracılığıyla videoCall alın. videoCall.connect() ve webRtcConnection.startRoom(context) çağırın.

    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
    
  2. Event Handling & UI State: videoCall.eventFlow (Kotlin) abone olun veya videoCall.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 veya ViewModel içinde saklayın.

  3. Çağrı Kontrolleri: Button tı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();
    
  4. 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ığı ve VideoRenderer composable'ında gösterildiği gibi lifecycle management için factory, update ve onRelease ile AndroidView kullanır.

    • Java/XML:

      1. XML layout'unuza <org.webrtc.SurfaceViewRenderer ... /> ekleyin.

      2. Renderers referanslarını alın (örn. ViewBinding kullanarak).

      3. Initialization: onCreate veya onViewCreated içinde, surfaceViewRenderer.init(webRtcConnection.getEglBase().getEglBaseContext(), null) çağırın.

      4. Attachment: Connection hazır olduğunda (örn. WebRtcConnected sonrasında veya view kullanılabilir olduğunda) webRtcConnection.attachLocalView(...) ve webRtcConnection.attachRemoteView(...) çağırın.

      5. Detachment & Release: Kritik olarak, onDestroy veya onDestroyView içinde, hem local hem de remote renderers için webRtcConnection.detachLocalView(...), webRtcConnection.detachRemoteView(...) ve surfaceViewRenderer.release() çağırın. Bunu doğru yapmamak leak'lere ve crash'lere neden olur.

  5. Immersive Mode: onResume/onPause veya ilgili lifecycle events'de system bars gizlemek/göstermek için WindowInsetsControllerCompat kullanın.

  6. Cleanup: onDestroy içinde, çağrı hala aktifse videoCall.hangup() çağrıldığından emin olun. Yukarıda belirtildiği gibi renderers release edin. videoCall.subscribe'ın LifecycleOwner'ı 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()) }
            }
        }
    }
    

Sonuç

Demo uygulama, ECV SDK'yı kullanmak için pratik, ancak basitleştirilmiş bir yapı sunar. Anahtar entegrasyon noktaları şunları içerir:

  • ECVSdk instance'ını initialize etme ve yönetme.

  • VideoCall instance'larını oluşturma ve yönetme.

  • Permissions yönetme.

  • Çağrıları uygun parameters ile başlatma.

  • UI state'ini ve navigation'ı yönlendirmek için VideoCallEvent'lere abone olma ve tepki verme (Kotlin Flows veya LifecycleOwner ile Java Callbacks kullanarak).

  • UI framework'ünüz içinde SurfaceViewRenderer'ların lifecycle'ını doğru şekilde yönetme (özellikle init, attach/detach ve release).

  • User actions yanıt olarak SDK method'larını (hangup, setAudioEnabled, vb.) çağırma.

Tam bir anlayış için diğer dokümantasyon dosyalarını (SDK_Kullanimi.md, Yapilandirma.md, Olaylar_ve_Durum_Yonetimi.md) okurken demo koduna geri başvurun. Burada gösterilen kavramları kendi uygulamanızın özel mimarisine (Compose veya View tabanlı) uyarlayın.

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:

  1. Kotlin (Flow): videoCall.eventFlow'a (SharedFlow<VideoCallEvent>) erişin ve Kotlin Coroutines kullanarak, tipik olarak bir LaunchedEffect veya lifecycleScope içinde events toplayın.

  2. Java (Callback): videoCall.subscribe(lifecycleOwner, eventCallback) method'unu, bir Android LifecycleOwner (Activity veya Fragment gibi) ve bir EventCallback implementation sağlayarak kullanın. Subscription, LifecycleOwner tarafından otomatik olarak yönetilir.

Her iki subscription method'unun ayrıntılı kod örnekleri için SDK_Kullanimi.md'deki "Event Handling" bölümüne bakın.

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:

  • type: CallEventTypes: (Gerekli) Gerçekleşen event türünü gösteren bir enum değeri. Nasıl tepki vereceğinizi belirlemek için kontrol edeceğiniz birincil field'dır.

  • detail: String?: (İsteğe bağlı) Event'e özgü ek string bilgisi sağlar (örn. GotRtcId için rtcId, DialSuccess için bir interaction ID, CallEnded için bir neden).

  • error: ECVError?: (İsteğe bağlı) Yalnızca Error events için dahil edilir. Bir ErrorTypes enum ve açıklayıcı bir mesaj içeren bir ECVError object'i içerir.

  • objData: Any?: (İsteğe bağlı) SignalingConnected sırasında WebRtcConnection veya belirli hatalarda bir HTTP response object'i gibi ilgili object'leri içerebilir. Bu data'ya erişiyorsanız tip kontrolü (is veya instanceof) kullanın.

  • data: Map<String, String>: (İsteğe bağlı, genellikle boş) Event ile ilişkili potansiyel key-value data için genel bir map.

  • createdAtMillis: Long: Event'in SDK içinde oluşturulduğu timestamp (epoch milisaniye).

  • videoCall: VideoCall: (Dahili olarak ayarlanır) Event'i yayan VideoCall instance'ına geri bir reference.

  • rtcId: String: (Dahili olarak ayarlanır) Event ile ilişkili çağrının rtcId'si.

Event Type Referansı (CallEventTypes)

İşte yaygın event types bir dökümü, tipik bir çağrının phase'ine göre gruplandırılmıştır:


Phase 1: Initialization & Dialing

  • GotRtcId

    • 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.

  • Error (Init/Dial Sırasında)

    • Ne zaman: rtcId alma 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, ECVError içerir. event.detail daha fazla context sağlayabilir. event.objData, bir HTTP response tutabilir. Spesifik ErrorTypes için error.error kontrol edin (CALL_NOT_INITIALIZED, DIAL_FAILED gibi).

    • Yanıt: User'a bir hata mesajı gösterin. Daha fazla çağrı ilerlemesini önleyin. Kullanılamaz durumdaysa VideoCall instance'ını potansiyel olarak temizleyin.


Phase 2: Queue & Agent Connection

  • OnQueue

    • 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:

      1. WebRtcConnection almak için videoCall.connect() çağırın.

      2. WebRTC setup'ını başlatmak için webRtcConnection.startRoom(context) çağırın.

      3. 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, ECVError içerir.

    • Yanıt: User'a çağrının bağlanamadığını bildirin. Queue ekranından geri gidin. SDK otomatik olarak CallEnded tetiklemezse cleanup sağlamak için videoCall.hangup() çağırın.

  • 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.detail bir neden içerebilir ("LOCAL_HANGUP", vb.).

    • Yanıt: Queue ekranından geri gidin. UI'nin temizlendiğinden emin olun.


Phase 3: WebRTC Connection Kurulumu

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.

  • SignalingConnected: Signaling server'ına WebSocket bağlantısı kuruldu.

  • SignalingRegistered: Client, room (rtcId) için signaling server ile başarıyla register oldu.

  • JoinedRoom: HTTP /join request başarılı, initial parameters alındı (ICE servers, initiator durumu gibi). event.objData, JoinRoomResponse içerir.

  • RtcPeerConnectionCreated: Temeldeki WebRTC PeerConnection object'i oluşturuldu. event.objData, PeerConnectionClient'ı tutar.

  • GotOffer / SentOffer: Peer'den SDP Offer alındı / gönderildi. event.objData, JSON mesajını içerir.

  • GotAnswer / SentAnswer: Peer'den SDP Answer alındı / gönderildi. event.objData, JSON mesajını içerir.

  • WebRtcConnected

    • 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 bir CallEnded event'ine yol açar.


Phase 4: Aktif Çağrı & Media

  • GotLocalStream: Yerel kamera capture başladı. event.objData, VideoCapturer içerebilir.

  • GotRemoteStream: Uzak media stream mevcut (genellikle WebRtcConnected civarında tetiklenir).

    • Yanıt: Uzak SurfaceViewRenderer'ın attach edildiğinden emin olun.

  • AudioMuted / AudioUnmuted: Yerel ses gönderme state'i değişti (setAudioEnabled tarafından tetiklenir).

    • Yanıt: Mute butonu UI state'ini güncelleyin.

  • VideoMuted / VideoUnmuted: Yerel video gönderme state'i değişti (setVideoEnabled tarafı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 (autoHandleHoldSideEffects true ise) yanıt olarak yerel medyayı otomatik olarak sessize alabilir/açabilir.

  • 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 (autoHandleCameraSwitch true ise) kameraları otomatik olarak değiştirebilir.

  • 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.


Phase 5: Çağrı Sonlandırma

  • Hangup

    • 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.detail genellikle 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:

      1. Tüm çağrıyla ilgili UI'yi (renderers, kontroller) temizleyin.

      2. Çağrı ekranından uzaklaşın.

      3. Belirli VideoCall'a bağlı tüm resources serbest bırakın. SDK tipik olarak bu event'ten sonra VideoCall instance'ına olan dahili reference'ını kaldırır.

State Properties

Events değişiklikleri bildirirken, bazı mevcut state properties de kontrol edebilirsiniz:

  • VideoCall Üzerinde:

    • 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 (son setAudioEnabled çağrısını yansıtır).

    • videoEnabled: Boolean: Yerel video göndermenin mevcut state'i (son setVideoEnabled çağrısını yansıtır).

    • isClosing: Boolean: Bu connection için cleanup process'i başladıysa true.

Genellikle UI state'inizi güncellemek için bu properties sürekli poll etmek yerine events'e tepki vermek en iyi practice'dir. Properties gerektiğinde initial state kontrolleri veya kararlar için kullanın (örn. connect() çağırmadan önce agentConnectedState kontrol etmek).

Ö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:

  • Jetpack Compose

  • Geleneksel Android Views (Activities/Fragments ile XML layouts)

  • Bunların herhangi bir kombinasyonu.

UI'niz İçin Anahtar Hususlar:

  • Kendi UI'nizi Oluşturun: Demo UI ekranlarını basitçe kopyalamayın. Uygulamanızın görünümüne, hissine ve user flow'una uyan ekranlar tasarlayın.

  • Temel SDK Etkileşimleri Aynı Kalır: UI toolkit'iniz ne olursa olsun, SDK ile temel etkileşimler devam eder:

    • 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.

2. Configuration (ECVSdkConfiguration)

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:

  1. Sabit Configuration: Uygulamanız yalnızca tek bir backend environment'ına (örn. production) bağlanıyorsa, ECVSdk'yı initialize ederken ECVSdkConfiguration değerlerini hardcode edebilirsiniz.

    // Ö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
    
  2. Dinamik Configuration: ECVSdk'yı initialize etmeden önce kendi uygulama backend'inizden configuration ayrıntılarını ( serviceUrl, webrtcUrl gibi) 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.

    • ECVSdkConfiguration oluşturmak için alınan URL'leri kullanır.

    • ECVSdk'yı initialize eder.

3. Çağrı Data (DialCallParameters)

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:

  • Navigation: AgentConnected, CallEnded, Error üzerinde ekran değişikliklerini tetikleyin.

  • UI Updates: Mute durumu, hold durumu, connection state değişikliklerini görsel olarak yansıtın.

  • Error Display: Error events meydana geldiğinde user dostu mesajlar gösterin.

  • Resource Cleanup: UI'yi temizlemek ve potansiyel olarak çağrıya özgü resources serbest bırakmak için kesin sinyal olarak CallEnded'i kullanın.

5. Gelişmiş Özelleştirme (İsteğe bağlı)

  • Custom Data Channel Mesajları: Backend'iniz text data channel aracılığıyla custom komutlar gönderiyorsa (HOLD, FLASH vb. ötesinde), TextChannelMessage event'ini dinleyebilir ve custom uygulama davranışı implement etmek için event.detail parse edebilirsiniz.

  • ConnectParameters Ayarlama: Belirli network environment'ları veya kalite gereksinimleri için, ConnectParameters'daki videoMaxBitrate, codecs veya ICE filtering modes gibi parameters ayarlamayı deneyebilirsiniz. Bu gelişmiş seçeneklerin ayrıntıları için WebRTC dokümantasyonuna başvurun.

  • Audio Management: SDK dahili olarak AppRTCAudioManager kullanır. ConnectParameters'da defaults (useSpeakerPhone, autoSwitchToEarPiece) sağlansa da, daha karmaşık audio routing, temel SDK kullanımının kapsamı dışında daha derin entegrasyon veya özelleştirme gerektirebilir.

6. Permissions

Her zaman Android'in permissions isteme için en iyi practice'lerini izleyin:

  • Yalnızca gerektiğinde permissions isteyin (çağrıyı başlatmadan hemen önce).

  • Permissions (Kamera, Mikrofon) neden gerekli olduğunu açıkça açıklayın.

  • User permissions reddettiği durumları zarifçe ele alın (örn. arama işlevini devre dışı bırakın, ayarlarda nasıl etkinleştirileceğine dair talimatlar sağlayın).

7. Ek Modüller

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;

  1. Kaynak çağrı dial edilir. (çağrı oluşturma)
  2. Kaynak çağrı kuyrukta bekler.
  3. Kaynak çağrı bağlanır
  4. Agent transfer başlatır
    1. Transfer kuyruğa veya doğrudan farklı bir agent'a olabilir.
    2. Doğrudan agent'a yapılan transfer iptal edilebilir veya agent tarafından reject edilebilir.
  5. videoCall.eventFlow üzerinden "TransferPending" eventi gelir
    1. 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.
    2. Bu event sonrasında T anında TransferCalceled eventi gelebilir, cancel eventi geldiğinde gösterilen uyarı gizlenmelidir. Çağrı aynen devam edecektir.
    3. 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.
  6. Transfer tamamlandığında eventFlow üzerinden TransferCompleted eventi gelecektir. event.detail içerisinde transfer edilen çağrıya atanan yeni RtcId değeri bulunmaktadır.
    1. Mevcut çağrı view'i ve bağlı bütün kaynaklar destroy edilir.
    2. Kullanıcı tekrar kuyruk sayfasına, bu sefer yeni RtcId ile gönderilir.
    3. 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ı

  1. Hata: Could not find :ECV-175-SDK-vX.Y.Z: (veya diğer AAR'lar)

    • 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.

  2. Hata: Unresolved reference: lifecycle veya androidx.lifecycle ile ilgili benzer hatalar

    • Neden: Gerekli androidx.lifecycle:lifecycle-runtime-ktx dependency'si uygulamanızın build dosyasında eksik. SDK bunu dahili olarak kullanır ancak api aracılığıyla expose etmez.

    • Çözüm: implementation("androidx.lifecycle:lifecycle-runtime-ktx:VERSION") dependency'sini uygulama düzeyindeki build.gradle/.kts dosyanıza ekleyin, VERSION yerine projenizle uyumlu güncel bir stabil version (örn. 2.7.0) yazın. Detaylar için README_tr.md'ye bakın. Gradle'ı senkronize edin.

  3. 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.

  4. 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'inin CAMERA, RECORD_AUDIO, INTERNET, MODIFY_AUDIO_SETTINGS ve potansiyel olarak BLUETOOTH_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.

Runtime Crashes

  1. Crash: UnsatisfiedLinkError (org.webrtc veya native method'larla ilgili)

    • 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.

  2. 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 önce ECVSdk(...) çağrıldığından emin olun. Log'larda initialization hatalarını kontrol edin.

      • getVideoCall Null Döndürüyor: rtcId geçersiz olabilir, çağrı zaten sona ermiş olabilir (CallEnded event'i) veya SDK instance'ı yeniden initialize edilmiş olabilir. Özellikle bir Activity/Fragment'ı resume ederken veya navigate ederken VideoCall object'ini kullanmadan önce getVideoCall(rtcId)'nin dönüş değerini her zaman null için kontrol edin.

      • WebRtcConnection Null: videoCall.connect()'in AgentConnected event'inden sonra ve webRtcConnection'a erişmeden önce başarıyla çağrıldığından emin olun. connect() veya startRoom() process'i sırasındaki hataları kontrol edin.

      • Context Null: Gerektiğinde geçerli Context iletildiğinden emin olun (örn. startRoom).

  3. 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 (onRelease Compose AndroidView'da, onDestroyView/onDestroy Fragments/Activities için) çağrıldığından emin olun.

      • Release Öncesi Detach: Kritik olarak, release() çağırmadan önce renderer üzerinde webRtcConnection.detachLocalView(...) ve webRtcConnection.detachRemoteView(...) çağırın.

      • Threading: init, attach/detach ve release'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.

  4. Crash: SecurityException (Permission Reddi)

    • Neden: User tarafından verilen gerekli runtime permissions olmadan kamerayı veya mikrofonu başlatmaya çalışmak.

    • Çözüm:

      • videoCall.start() veya webRtcConnection.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.

Media Quality Sorunları

  1. Kötü Video/Ses Kalitesi (Lag, Donma, Pixelation, Bozuk Ses):

    • 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).

Özel Feature Sorunları

  1. Kamera Değiştirme Çalışmıyor:

    • 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.

  2. Hold/Pickup Otomatik Olarak Sessize Almıyor/Açmıyor:

    • Debugging:

      • ConnectParameters içinde autoHandleHoldSideEffects = true olduğundan emin olun (bu default'tur).

      • StartHold / EndHold events doğru şekilde alınıp alınmadığını kontrol edin.

      • "HOLD|" veya "PICKUP|" ile ilgili TextChannelMessage events alınıp alınmadığını kontrol edin. Text channel'ın bağlı olduğunu doğrulayın (TextChannelConnected event'i).

Genel Debugging Adımları

  1. Logcat'i Kontrol Edin: Bu en önemli aracınızdır. SDK'da kullanılan tag'lere (ECVSdk, VideoCall, WebRtcConnection, Signaling, QueueSocket, PeerConnectionClient, AppRTCAudioManager) ve kendi uygulama tag'lerinize (AppState, MainScreen, CallScreen, vb.) göre filtreleyin. Warnings ve errors arayın. Gerekirse loglama ayrıntı düzeyini artırın (SDK standart Android loglama seviyelerini kullanır).

  2. Configuration Doğrulayın: ECVSdkConfiguration (URL'ler, useQueueSocket), DialCallParameters (attributes, queue) ve herhangi bir custom ConnectParameters hedef environment'ınız ve gereksinimlerinizle dikkatlice karşılaştırın.

  3. Network Connectivity Kontrol Edin: Cihaz serviceUrl, webrtcUrl ve signalingUrl'e ulaşabiliyor mu? Browser/ping/network araçları kullanın. Wi-Fi/Cellular connection durumunu kontrol edin. Farklı network'leri test edin. Varsa kurumsal firewall'lar hakkında bilgi alın.

  4. Basitleştirin: Karmaşık UI'yi, custom logic'i veya isteğe bağlı SDK feature'larını geçici olarak kaldırın. En temel, default configurations kullanın (ECVSdkConfiguration, DialCallParameters, ConnectParameters). Basit bir çağrı çalışıyor mu? Karmaşıklığı kademeli olarak yeniden ekleyin.

  5. Phase'i İzole Edin: Hatanın ne zaman meydana geldiğini belirleyin: SDK init? Dialing? Queue? WebRTC connection? Media rendering? Bu, aramayı daraltmaya yardımcı olur.

  6. Demo Uygulamasına Başvurun: Implementation logic'inizi, özellikle event handling, navigation ve SurfaceViewRenderer lifecycle management etrafında, sağlanan demo kaynak koduyla dikkatlice karşılaştırın.

  7. Tüm Events Loglayın: Aldığınız her VideoCallEvent'i yazdırmak için event handling callback/collector'ınızın içine loglama ekleyin. Bu, çağrı flow'unu izlemeye ve nerede yanlış gidebileceğini veya hangi beklenen events eksik olduğunu görmeye yardımcı olur.

Desteğe Başvurma

Bu adımları izledikten sonra sorunlarla karşılaşmaya devam ederseniz, lütfen desteğe başvururken aşağıdaki bilgileri hazırlayın:

  • SDK Version: (örn. ECV-175-SDK-v1.2.3)

  • Test Edilen Cihaz(lar): Üretici, Model, Android OS Version.

  • Backend Version: v1.75+

  • Configuration: ECVSdkConfiguration'ınız, ilgili DialCallParameters ve herhangi bir custom ConnectParameters.

  • Ayrıntılı Sorun Açıklaması: Belirtiler nelerdir? Sorunu hangi adımlar yeniden oluşturuyor? Beklenen ve gerçek davranış nedir?

  • İlgili Logcat Output: Sorunun meydana geldiği sırada, SDK tag'lerini ve uygulama tag'lerinizi içeren log'ları yakalayın. Mümkünse uygun şekilde filtreleyin, ancak context sağlayın.

  • Screenshots/Videolar: Sorunu göstermeye yardımcı olacaksa.