Skip to main content

ECV Video Görüşme iOS SDK

by  Zeynep Baysun, last modified by  Tarık Seyceri on Jun 16, 2025
image-20240102-113213.pngimage-20240102-113321.png

ECV Video Görüşme iOS SDK

Giriş

ECV Video Görüşme iOS SDK'sına hoş geldiniz! Bu SDK, gerçek zamanlı video görüşme işlevselliğini iOS uygulamalarınıza sorunsuz bir şekilde entegre etmenizi sağlar. Sinyal iletimi, WebRTC eş bağlantıları, medya akışları ve görüşme durumu yönetimi gibi karmaşık işlemleri halleder, böylece siz uygulamanızın kullanıcı arayüzü ve iş mantığına odaklanabilirsiniz.

Bu depo şunları içerir:

Temel Özellikler

Başlangıç

Ön Koşullar

1. Gerekli Framework'leri Ekleyin

Uygulamanızın ECV SDK framework'ünü ve gerekli bağımlılıklarını içermesi gerekir: WebRTC. Bunları .framework dosyaları olarak alacaksınız.

2. Demo Uygulamayı İnceleyin

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

3. Temel SDK Başlatma (Kavramsal)

// AppDelegate veya merkezi bir yönetim nesnesinde
import ECV175_iOS_SDK

// 1. SDK Yapılandırmasını Tanımlayın
let sdkConfig = ECVSdkConfiguration(
    name: "Environment_Name" // Uygulamanızı veya backend'inizi ifade eden herhangi bir isim verin
    serviceUrl: "YOUR_SERVICE_URL" // Gerçek servis URL'niz ile değiştirin
    // İsteğe bağlı olarak webrtcUrl, signalingUrl vb. değerleri geçersiz kılın
)

// 2. SDK Örneğini Başlatın
// Merkezi olarak yönetilen tek bir örnek olması önerilir
let ecvSdk = ECVSdk(config: sdkConfig)

// Bu örneği uygulamanız genelinde erişim için saklayın (örn., AppDelegate, DI, Singleton)
// Örnek: MyApp.sdkInstance = ecvSdk

Temel Kavramlar

Demo Uygulama Akışı

Demo uygulama şu temel akışı izler:

SDK Kullanım Kılavuzu

Bu belge, ECV Video Görüşme iOS SDK'sını uygulamanızda kullanma hakkında detaylı bilgi sağlar.

SDK Başlatma

Temel Başlatma

import ECV175_iOS_SDK

// SDK yapılandırmasını oluştur
let config = ECVSdkConfiguration(
    name: "Environment_Name" // Uygulamanızı veya backend'inizi ifade eden herhangi bir isim verin
    serviceUrl: "YOUR_SERVICE_URL"
)

// SDK örneğini başlat
let sdk = ECVSdk(config: config)

Yapılandırma Seçenekleri

ECVSdkConfiguration sınıfı çeşitli yapılandırma seçeneklerini destekler:

let config = ECVSdkConfiguration(
    name: "Environment_Name",
    serviceUrl: "YOUR_SERVICE_URL",
    webrtcUrl: "YOUR_WEBRTC_URL", // İsteğe bağlı, varsayılan: serviceUrl
    signalingUrl: "YOUR_SIGNALING_URL" // İsteğe bağlı, varsayılan: serviceUrl
)

Ek Yapılandırmalar

Logger.setLevel(logLevel: .debug) // .debug SDK'daki her şeyin hata ayıklamasını etkinleştirmek için (.debug, .info, .warn, .error) // varsayılan: .info

Görüşme Oluşturma ve Yönetme

Görüşme Oluşturma

// Video görüşmesini oluştur ve başlat
let call = await sdk.createVideoCall() 

Eğer zaten bir rtcId'niz varsa, bunu createVideoCall fonksiyonuna sağlayabilirsiniz, aksi takdirde otomatik olarak backend'den oluşturulacaktır (varsayılan)

let call = await sdk.createVideoCall(rtcId: "your_rtcId")

VideoCall Olayları

Şimdi görüşmeyi başlatmadan önce görüşmeye olay dinleyicisi ekleyebiliriz

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call.rtcId){
        return // Sadece mevcut görüşmeyle ilgili olayları yakaladığımızdan emin oluyoruz. // tüm görüşmelerin olaylarını yakalamak için bu kodu yorum satırına alın
    }

    if(newEvent.type == .AgentConnected) {
        Logger.info("From sdk.modules.onCallEvent: \(newEvent.type)")
    }
    else if(newEvent.type == .Error){
        Logger.error("From sdk.modules.onCallEvent: \(newEvent.detail ?? "")")
    }
    else {
        Logger.info("From sdk.modules.onCallEvent: \(newEvent)")
    }
}

VideoCall Parametreleri

Görüşmeyi başlatmak için DialCallParameters nesnesini hazırlamamız gerekir

let dialCallParameters = DialCallParameters(
    callerName: "Caller_Name",      // İsteğe bağlı ama sağlanmalı, aksi takdirde rastgele bir isim oluşturulacak
    attributes: [String: String],   // İsteğe bağlı, Görüşme için veri ekleyin
    queue: String?,                 // İsteğe bağlı, Kuyruk adı, sağlanmazsa sistemin varsayılan kuyruğunu kullanacak
    
    // Gelişmiş İsteğe Bağlı Parametreler
    acdAttributes: [String: String],
    customPayload: [String: String],
    additionalHeaders: [String: String],
)

Örnek:

let dialCallParameters = DialCallParameters(
    callerName: "John Doe",
    attributes: [
        "department": "support",
        "priority": "high"
    ],
    queue: "TechSupport",
)

VideoCall'u Başlatma

ECV 1.75'te bir VideoCall başlatmak istiyorsak, call.start kullanırız

_ = await call.start(dialCallParameters: dialCallParameters)

Not: Sonucu _'ye atadık çünkü call.start queueSocket bağlantısını [String: Any]? verisini döndürür. Şimdilik bunları işlememize gerek yok. (Çok Gelişmiş)

Bir Temsilcinin bağlanmasını beklemek (inQueue)

Bir temsilcinin bağlanmasını beklemek için iki basit yol vardır:

1- Yukarıda zaten uyguladığımız VideoCall olaylarını kullanarak if(newEvent.type == .AgentConnected)

2- Beklenen promise tabanlı görevi kullanarak:

let isAgentConnected = await call.agentConnected?.value
Logger.info("isAgentConnected: \(String(describing: isAgentConnected))")

if(isAgentConnected == true) {
    // Görüşmeyi ayrı bir web tarayıcısında açmak belki? // Geliştirme için faydalı
    if let urlString = call.getUrl(), let url = URL(string: urlString) {
        UIApplication.shared.open(url, options: [:], completionHandler: nil)
    }
    else {
        Logger.error("Failed to open URL: invalid URL string")
    }
}
else {
    Logger.warn("Agent not connected!")
}

VideoCall'u Bağlama

1- videoCall.connect() kullanarak

CallScreen.swift dosyanızda

func setup() {
    Task {
        guard let rtcId = self.rtcId else {
            Logger.error("Setup failed: RtcId is nil")
            return
        }

        // Ekranlar arasında gezinirken ve zaten bir video görüşmesi oluşturduysanız, video görüşmesini sdk içinde rtcId kullanarak bulabilirsiniz.
        if let videoCall = appState.sdk?.getVideoCall(rtcId: rtcId){
            self.videoCall = videoCall
        }
        else {
            // Veya yeni bir videoCall nesnesi oluşturmak istiyorsanız ve rtcId'niz varsa, ve temsilcinin odada beklediğinden eminseniz // geliştirme için faydalı
            self.videoCall = await appState.sdk?.createVideoCall(rtcId: rtcId)
        }

        guard let webRtcConnection = self.videoCall?.connect() else {
            self.callError = "WebRtcConnection failed to connect"
            Logger.error("Setup failed: WebRtcConnection is nil")
            return
        }
        self.webRtcConnection = webRtcConnection

        Logger.debug("Instantiating and assigning renderers...")
        let local = Renderer()
        let remote = Renderer()

        webRtcConnection.localVideoRenderer = local
        webRtcConnection.remoteVideoRenderer = remote

        self.localRenderer = local
        self.remoteRenderer = remote

        Logger.info("Starting WebRTC room...")
        await webRtcConnection.startRoom()
        Logger.info("Call setup complete.")
    }
}

2- Veya arayan web tarayıcısı kullanarak video görüşmesine katılabilir (önerilmez / geliştirme için faydalı)

if let urlString = call.getUrl(), let url = URL(string: urlString) {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
else {
    Logger.error("Failed to open URL: invalid URL string")
}

Video Görüşme Arayüzü

Video Görünümlerini Ayarlama

import Foundation
import SwiftUI
import Combine
import ECV175_iOS_SDK
import WebRTC

struct CallScreen: View {
    let rtcId: String?
    @EnvironmentObject var appState: AppState

    @Binding var path: NavigationPath

    @State private var videoCall: VideoCall?
    @State private var webRtcConnection: WebRtcConnection?

    @State private var localRenderer: Renderer?
    @State private var remoteRenderer: Renderer?

    let localStreamWidth: CGFloat = 80
    let localStreamHeight: CGFloat = 120
    let margin: CGFloat = 10
    let pipBottomPadding: CGFloat = 70
    
    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()

            WebRtcVideoView(renderer: remoteRenderer).ignoresSafeArea()

            WebRtcVideoView(renderer: localRenderer)
                .frame(width: localStreamWidth, height: localStreamHeight)
                .cornerRadius(8)
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(Color.white.opacity(0.5), lineWidth: 1)
                )
                .padding(.trailing, margin)
                .padding(.bottom, margin + pipBottomPadding)

            // ... Görüşme Düğmeleri
        }
        .navigationBarHidden(true)
        .statusBar(hidden: true)
        .onAppear(perform: setup)
        .onDisappear {
            hangup(by: "onDisappear")
        }
    }

    // MARK: - Video Renderer SwiftUI Sarmalayıcısı

    struct WebRtcVideoView: UIViewRepresentable {
        let renderer: (any RTCVideoRenderer)?

        func makeUIView(context: Context) -> UIView {
            if let renderer = renderer as? Renderer {
                return renderer.view
            } else if let renderer = renderer as? Renderer {
                return renderer.view
            } else {
                return UIView()
            }
        }

        func updateUIView(_ uiView: UIView, context: Context) {}
    }

    // MARK: - Özel Renderer Sınıfları

    class Renderer: NSObject, RTCVideoRenderer {
        let view: RTCMTLVideoView

        override init() {
            self.view = RTCMTLVideoView()
            self.view.videoContentMode = .scaleAspectFill
            super.init()
            Logger.debug("Renderer created")
        }

        deinit { Logger.debug("Renderer deallocated") }

        func setSize(_ size: CGSize) {
            view.setSize(size)
        }

        func renderFrame(_ frame: RTCVideoFrame?) {
            view.renderFrame(frame)
        }
    }

    func hangup(by: String = "HangUpButton") {
        Logger.debug("Hangup requested by: \(by)")

        videoCall?.hangup()

        videoCall = nil
        webRtcConnection = nil
        localRenderer = nil
        remoteRenderer = nil
    }

    // func setup(){
    // ... buraya yapıştırın
    //}
}

Görüşme Kontrolleri

// Sesi Kapat/Aç
webRtcConnection.setAudioEnabled(state: false) // Sesi kapat
webRtcConnection.setAudioEnabled(state: true)  // Sesi aç

// Videoyu Devre Dışı Bırak/Etkinleştir
webRtcConnection.setVideoEnabled(state: false) // Videoyu devre dışı bırak
webRtcConnection.setVideoEnabled(state: true)  // Videoyu etkinleştir

// Kamerayı Değiştir
try await webRtcConnection.switchCamera()

// Flaşı Etkinleştir/Devre Dışı Bırak
webRtcConnection.setFlashlightEnabled(state: true)
webRtcConnection.setFlashlightEnabled(state: false)

// Görüşmeyi Sonlandır
videoCall.hangup()

Video Görüşme Olay İşleme

sdk.modules.onCallEvent'te bir olay tetiklendiğinde, newEvent VideoCallEvent ve newEvent.type VideoCallEventType'dır

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call.rtcId){
        return // Sadece mevcut görüşmeyle ilgili olayları yakaladığımızdan emin oluyoruz. // tüm görüşmelerin olaylarını yakalamak için bu kodu yorum satırına alın
    }

    if(newEvent.type == .DialSuccess){
        Logger.error("From sdk.modules.onCallEvent: \(newEvent.detail ?? "")")
    }
    else if(newEvent.type == .CallEnded){
        // işle
    }
    //else if(){

    //}
}

her tip kendini açıklayıcıdır:

public enum VideoCallEventType {
    case None
    case Init
    case GotRtcId
    case DialSuccess
    case Error
    case OnQueue
    case AgentConnected
    case CallEnded
    case SignalingConnected
    
    case GotLocalStream
    case GotRemoteStream
    
    case JoinedRoom
    case SignalingRegistered
    case RtcPeerConnectionCreated
    
    case GotOffer
    case SentOffer
    case GotAnswer
    case SentAnswer
    
    case AudioMuted
    case AudioUnmuted
    case VideoMuted
    case VideoUnmuted
    
    case RemoteAudioMuted
    case RemoteAudioUnmuted
    case RemoteVideoMuted
    case RemoteVideoUnmuted
    
    case StartHold
    case EndHold
    
    case TakePhotoOn
    case TakePhotoOff
    
    case WebRtcOnDataChannel
    case TextChannelConnected
    case TextChannelMessage
    case ConnectionTimeoutStart
    case ConnectionTimeoutEnd
    case WebRtcConnected
    case WebRtcRenegotiationNeeded

    case WebRtcDisconnected
    case WebRtcFailed
    case Hangup
    
    case TransferPending
    case TransferCanceled
    case TransferredTo
}

Hata Yönetimi

Hataları daha kesin bir şekilde yönetmek için, .onCallEvent altında bir hata oluştuğunda videoCall.lastError'ı kontrol edebiliriz

Yapılandırma Kılavuzu

Bu belge, ECV Video Görüşme iOS SDK'sında mevcut yapılandırma seçeneklerini detaylandırır.

SDK Yapılandırması

Temel Yapılandırma

ECVSdkConfiguration sınıfı video görüşme örneğini yapılandırmak için kullanılır:

let config = ECVSdkConfiguration(
    name: "Environment_Name" 
    serviceUrl: "YOUR_SERVICE_URL",
)

Gerekli Parametreler

İsteğe Bağlı Parametreler

Örnek Kullanımlar:

let configV175 = ECVSdkConfiguration(
    name: "Some ECV 1.75 App" 
    serviceUrl: "https://prod-v175.ecv.example.com"
)

let configSeparateV175 = ECVSdkConfiguration(
    name: "Some custom ECV 1.75 App",
    serviceUrl: "https://api.example.com",
    webrtcUrl: "https://webrtc-v15.example.com", // /join için ayrı endpoint
    signalingUrl: "https://websocket-v15.example.com" // Ayrı WebSocket sunucusu
)

Görüşme Yapılandırması

Temel Görüşme Yapılandırması

DialCallParameters sınıfı bireysel görüşmeleri yapılandırmak için kullanılır:

let dialCallParameters = DialCallParameters() // Gerekli parametre yok

İsteğe Bağlı DialCallParameters Parametreleri

Örnek Kullanımlar:

let dialCallParameters = DialCallParameters(
    callerName: "Jane Doe",
    attributes: [
        "accountNumber" to "ACC9876",
        "lastInteraction" to "2023-10-26"
    ],
    queue: "SupportQueue",
    additionalHeaders: ["X-Auth-Token": "user_session_token"]
)

await call.start(dialCallParameters: dialCallParameters)

WebRTC Bağlantı Parametreleri

Bu parametreler videoCall.connect() çağrıldığında isteğe bağlı olarak sağlanır ve WebRTC eş bağlantı davranışını ince ayar yapar. Çoğu parametrenin makul varsayılanları vardır.

Örnek Kullanım:

let customConnectParams = ConnectParameters(
    videoWidth: 1280,
    videoHeight: 720,
    videoCodec: "H264",
    additionalHeaders: ["X-WebRTC-Auth": "webrtc_token"]
)

// Özel parametreleri kullanarak WebRtcConnection'ı al
let webRtcConnection = videoCall.connect(parameters: customConnectParams)
webRtcConnection.startRoom()

DataChannelConfig

DataChannelConfig, eşler arası iletişim ortamında bir veri kanalını başlatmak için kullanılan bir yapılandırma modelidir.

Özellikler

Başlatıcı

init(
    enabled: Bool,
    label: String,
    id: Int = -1,
    negotiated: Bool = false,
    protocol: String = "",
    ordered: Bool = true,
    maxRetransmits: Int = -1,
    maxRetransmitTimeMs: Int = -1
)

Demo Uygulama Kılavuzu

Genel Bakış

ECV175_iOS_DEMO uygulaması, ECV Video Görüşme SDK'sının bir iOS uygulamasına nasıl entegre edileceğini ve kullanılacağını gösteren pratik bir örnek olarak hizmet eder. Bu kılavuz, uygulamanın mimarisini, temel bileşenlerini ve nasıl etkili bir şekilde kullanılacağını adım adım açıklar.

Uygulama Mimarisi

Temel Bileşenler

Temel Özellikler

Başlangıç

Ön Koşullar

Kurulum Süreci

Uygulama Akışı

Kod Yapısı

Temel Dosyalar

En İyi Uygulamalar

Sorun Giderme

SDK Entegrasyonu ve Teknik Detaylar

Temel Yapılandırma ve Derleme Sistemi

Ekran Bazlı SDK Entegrasyonu

SDK Sürüm Uyumluluğu

Olay İşleme Sistemi

SDK Entegrasyonu için En İyi Uygulamalar

Sonuç

ECV175_iOS_DEMO uygulaması, ECV Video Görüşme SDK'sını kullanarak video görüşme işlevselliğini uygulamak için sağlam bir temel sağlar. Bu kılavuzu takip ederek ve uygulamanın mimarisini anlayarak, kendi iOS uygulamalarınıza video görüşme özelliklerini etkili bir şekilde entegre edebilirsiniz.

Belirli özellikler veya uygulama detayları hakkında daha ayrıntılı bilgi için lütfen SDK dokümantasyonuna ve API referansına bakın.

Olaylar ve Durum Yönetimi

Giriş

ECV iOS SDK asenkron olarak çalışır. Durum değişikliklerini, hataları ve diğer önemli olayları uygulamanıza iletmek için olay tabanlı bir mimari kullanır. Bu olayları anlamak, duyarlı ve sağlam bir kullanıcı arayüzü oluşturmak için çok önemlidir.

Bu kılavuz, SDK olaylarının yapısını detaylandırır, temel olay türlerini listeler ve ilgili durum özelliklerini belirtir.

Olaylara Abone Olma

Olayları sdk.modules.onCallEvent örneği üzerinde dinlersiniz:

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call?.rtcId){
        return // Sadece mevcut görüşmeyle ilgili olayları yakaladığımızdan emin oluyoruz. // tüm görüşmelerin olaylarını yakalamak için bu kodu yorum satırı yapın
    }
    
    if(newEvent.type == .Error){
        // Hata olayını işle
    }
    else if(newEvent.type == .AgentConnected) {
        // AgentConnected olayını işle
    }
    else {
        print("ECVModules?.onCallEvent'ten: \(newEvent)")
    }
}

Her iki abonelik yöntemi için detaylı kod örnekleri için "Video Görüşme Olay İşleme" bölümüne bakın.

Olay Yapısı (VideoCallEvent)

Temel Alanlar:

Olay Türü Referansı (VideoCallEventType)

İşte yaygın olay türlerinin, bir görüşmenin tipik aşamalarına göre gruplandırılmış bir dökümü:

Aşama 1: Başlatma ve Arama

Aşama 2: Kuyruk ve Temsilci Bağlantısı

Aşama 3: WebRTC Bağlantı Kurulumu

Bu olaylar genellikle WebRtcConnection tarafından dahili olarak işlenir ancak hata ayıklama veya gelişmiş kullanım durumları için gözlemlenebilir. Başarıyı işaret eden temel olay WebRtcConnected'dır.

Aşama 4: Aktif Görüşme ve Medya

Aşama 5: Görüşme Sonlandırma

Durum Özellikleri

Olaylar değişiklikleri sinyal ederken, bazı mevcut durum özelliklerini de kontrol edebilirsiniz:

Genellikle ara yüz durumunuzu güncellemek için olaylara tepki vermek, bu özellikleri sürekli kontrol etmekten daha iyi bir uygulamadır. Bu özellikleri başlangıç durumu kontrolleri veya gerektiğinde kararlar için kullanın (örn. connect()'i çağırmadan önce AgentConnected'ı kontrol etmek).

Özelleştirme Kılavuzu

Giriş

Sağlanan Demo Uygulama (ECV175_iOS_DEMO), ECV SDK'nın temel işlevlerini gösterir. Ancak, üretim uygulamanızın kesinlikle farklı UI/UX gereksinimleri, yapılandırma yöntemleri ve veri ihtiyaçları olacaktır. Bu kılavuz, SDK ve demo kavramlarını kendi uygulamanıza uyarlamak için tavsiyeler sağlar.

1. UI Uygulaması

Demo SwiftUI kullanır, ancak SDK'nın kendisi UI'dan bağımsızdır. Bunu şunlarla oluşturulmuş uygulamalara entegre edebilirsiniz:

UI için Temel Hususlar:

2. Yapılandırma (ECVSdkConfiguration)

Demo'nun EnvironmentsScreen.swift'i, farklı backend ortamlarını kolayca test etmek için tasarlanmıştır. Üretim uygulamanızda muhtemelen bu ekrana ihtiyacınız olmayacaktır.

Üretim Yapılandırma Stratejileri:

3. Görüşme Verisi (DialCallParameters)

Demo, seçilen ortama göre görüşme verisi için açılır listeleri (kuyruk seçimi) dinamik olarak eklemek üzere Build.swift'te selectableQueues kullanır. Bu, demo'nun çoklu ortam kurulumuna özeldir.

Uygulamanızda, videoCall.start()'ı çağırmadan hemen önce DialCallParameters'ı ilgili bağlamsal verilerle doldurmalısınız.

Örnek:

// Kullanıcı profil verisi
val userId = "user123"
val accountType = "Premium"
val intent = "Fatura Sorgusu"

// Hedef kuyruk/beceri
val targetQueue = "BillingSupport"

// SDK örneğini al ve görüşme oluştur
val videoCall = sdkInstance.createVideoCall()
// ... GotRtcId için bekle ...

// Uygulamaya özel verilerle parametreleri oluştur
val dialParams = DialCallParameters(
    callerName = "User $userId", // Veya gerçek kullanıcı adı
    attributes = [
        "userId": userId,
        "accountType": accountType,
        "callIntent": intent
    ],
    acdAttributes = [
        "skill": "Billing" // Örnek ACD özelliği
    ],
    queue = targetQueue, // Hedef kuyruğu açıkça ayarla
    additionalHeaders = ["Authorization": "Bearer \(getUserAuthToken())"] // Örnek kimlik doğrulama
)

// Görüşmeyi başlat
videoCall.start(dialParams)

// Kuyruk/Bekleme ekranına git

4. Olay İşleme Mantığı

Demo olaylara nasıl abone olunacağını gösterirken, yanıt olarak ne yaptığınız uygulamaya özeldir. Uygulamanızın şu temel olayları doğru şekilde işlediğinden emin olun:

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

6. İzinler

Her zaman iOS'un izin isteme en iyi uygulamalarını takip edin:

Sorun Giderme Kılavuzu

Bu kılavuz, ECV iOS SDK'sını entegre ederken veya kullanırken karşılaşılan yaygın sorunlar için çözümler ve hata ayıklama adımları sağlar.

Yaygın Sorunlar ve Çözümleri

Derleme ve Bağımlılık Hataları

Çalışma Zamanı Çökmeleri

Medya Kalitesi Sorunları

Belirli Özellik Sorunları

Genel Hata Ayıklama Adımları

Destek ile İletişim

Destek ile iletişime geçerken lütfen şunları sağlayın: