← WebRTC 1.0 Spesifikasyonu

Örnekler ve Çağrı Akışları

W3C WebRTC 1.0 Spesifikasyonu — Türkçe Çeviri

10. Örnekler ve Çağrı Akışları

Bu bölüm normatif değildir.

10.1 Basit Eşler Arası Örnek

İki eş, birbirleriyle bir bağlantı kurmaya karar verdiklerinde, her ikisi de bu adımlardan geçer. STUN/TURN sunucu yapılandırması, genel IP adreslerini almak veya NAT geçişini ayarlamak gibi işlemler için kullanabilecekleri bir sunucuyu tanımlar. Ayrıca, en başta iletişim kuracaklarını belirlemek için kullandıkları aynı bant dışı mekanizmayı kullanarak sinyalizasyon kanalı verilerini birbirlerine göndermeleri gerekir.

const signaling = new SignalingChannel(); // JSON.stringify/parse işlemlerini yönetir
                        const constraints = {audio: true, video: true};
                        const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
                        const pc = new RTCPeerConnection(configuration);

                        // tüm ICE adaylarını diğer eşe gönder
                        pc.onicecandidate = ({candidate}) => signaling.send({candidate});

                        // "negotiationneeded" olayının teklif üretimini tetiklemesine izin ver
                        pc.onnegotiationneeded = async () => {
                          try {
                            await pc.setLocalDescription();
                            // teklifi diğer eşe gönder
                            signaling.send({description: pc.localDescription});
                          } catch (err) {
                            console.error(err);
                          }
                        };

                        pc.ontrack = ({track, streams}) => {
                          // uzak bir iz için medya geldiğinde, bunu uzak video öğesinde göster
                          track.onunmute = () => {
                            // eğer zaten ayarlıysa srcObject'i tekrar ayarlama.
                            if (remoteView.srcObject) return;
                            remoteView.srcObject = streams[0];
                          };
                        };

                        // başlatmak için start() çağrılır
                        function start() {
                          addCameraMic();
                        }

                        // kamerayı ve mikrofonu bağlantıya ekle
                        async function addCameraMic() {
                          try {
                            // yerel bir akış al, bunu kendi görünümünde göster ve gönderilmek üzere ekle
                            const stream = await navigator.mediaDevices.getUserMedia(constraints);
                            for (const track of stream.getTracks()) {
                              pc.addTrack(track, stream);
                            }
                            selfView.srcObject = stream;
                          } catch (err) {
                            console.error(err);
                          }
                        }

                        signaling.onmessage = async ({data: {description, candidate}}) => {
                          try {
                            if (description) {
                              await pc.setRemoteDescription(description);
                              // bir teklif aldıysak, bir yanıtla karşılık vermemiz gerekir
                              if (description.type == 'offer') {
                                if (!selfView.srcObject) {
                                  // izin alınana kadar müzakereyi engeller (üretim kodunda önerilmez)
                                  await addCameraMic();
                                }
                                await pc.setLocalDescription();
                                signaling.send({description: pc.localDescription});
                              }
                            } else if (candidate) {
                              await pc.addIceCandidate(candidate);
                            }
                          } catch (err) {
                            console.error(err);
                          }
                        };

10.2 Isındırma ile Gelişmiş Eşler Arası Örnek

İki eş, birbirleriyle bir bağlantı kurmaya karar verdiklerinde ve ICE, DTLS ve medya bağlantılarının, medyayı hemen gönderip alabilecek şekilde "ısıtılmış" olmasını istediklerinde, her ikisi de bu adımlardan geçer.

const signaling = new SignalingChannel(); // JSON.stringify/parse işlemlerini yönetir
                        const constraints = {audio: true, video: true};
                        const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
                        let pc;
                        let audio;
                        let video;
                        let started = false;

                        // Medya hazır olmadan önce warmup() çağrılarak ICE, DTLS ve medya ısıtılır.
                        async function warmup(isAnswerer) {
                          pc = new RTCPeerConnection(configuration);
                          if (!isAnswerer) {
                            audio = pc.addTransceiver('audio');
                            video = pc.addTransceiver('video');
                          }

                          // tüm ICE adaylarını diğer eşe gönder
                          pc.onicecandidate = ({candidate}) => signaling.send({candidate});

                          // "negotiationneeded" olayının teklif üretimini tetiklemesine izin ver
                          pc.onnegotiationneeded = async () => {
                            try {
                              await pc.setLocalDescription();
                              // teklifi diğer eşe gönder
                              signaling.send({description: pc.localDescription});
                            } catch (err) {
                              console.error(err);
                            }
                          };

                          pc.ontrack = async ({track, transceiver}) => {
                            try {
                              // uzak iz için medya geldiğinde, bunu video öğesinde göster
                              track.onunmute = () => {
                                // eğer zaten ayarlıysa srcObject'i tekrar ayarlama.
                                if (!remoteView.srcObject) {
                                  remoteView.srcObject = new MediaStream();
                                }
                                remoteView.srcObject.addTrack(track);
                              }

                              if (isAnswerer) {
                                if (track.kind == 'audio') {
                                  audio = transceiver;
                                } else if (track.kind == 'video') {
                                  video = transceiver;
                                }
                                if (started) await addCameraMicWarmedUp();
                              }
                            } catch (err) {
                              console.error(err);
                            }
                          };

                          try {
                            // yerel bir akış al, bunu kendi görünümünde göster ve gönderilmek üzere ekle
                            selfView.srcObject = await navigator.mediaDevices.getUserMedia(constraints);
                            if (started) await addCameraMicWarmedUp();
                          } catch (err) {
                            console.error(err);
                          }
                        }

                        // warmup() sonrasında start() çağrılarak her iki uçtan medya iletimi başlatılır
                        function start() {
                          signaling.send({start: true});
                          signaling.onmessage({data: {start: true}});
                        }

                        // önceden ısıtılmış bağlantıya kamera ve mikrofon ekle
                        async function addCameraMicWarmedUp() {
                          const stream = selfView.srcObject;
                          if (audio && video && stream) {
                            await Promise.all([
                              audio.sender.replaceTrack(stream.getAudioTracks()[0]),
                              video.sender.replaceTrack(stream.getVideoTracks()[0]),
                            ]);
                          }
                        }

                        signaling.onmessage = async ({data: {start, description, candidate}}) => {
                          if (!pc) warmup(true);

                          try {
                            if (start) {
                              started = true;
                              await addCameraMicWarmedUp();
                            } else if (description) {
                              await pc.setRemoteDescription(description);
                              // bir teklif aldıysak, bir yanıtla karşılık vermemiz gerekir
                              if (description.type == 'offer') {
                                await pc.setLocalDescription();
                                signaling.send({description: pc.localDescription});
                              }
                            } else {
                              await pc.addIceCandidate(candidate);
                            }
                          } catch (err) {
                            console.error(err);
                          }
                        };

10.3 Simulcast Örneği

Bir istemci, bir sunucuya birden fazla RTP kodlaması (simulcast) göndermek ister.

const signaling = new SignalingChannel(); // JSON.stringify/parse işlemlerini yönetir
                        const constraints = {audio: true, video: true};
                        const configuration = {'iceServers': [{'urls': 'stun:stun.example.org'}]};
                        let pc;

                        // başlatmak için start() çağrılır
                        async function start() {
                          pc = new RTCPeerConnection(configuration);

                          // "negotiationneeded" olayının teklif üretimini tetiklemesine izin ver
                          pc.onnegotiationneeded = async () => {
                            try {
                              await pc.setLocalDescription();
                              // teklifi diğer eşe gönder
                              signaling.send({description: pc.localDescription});
                            } catch (err) {
                              console.error(err);
                            }
                          };

                          try {
                            // yerel bir akış al, bunu kendi görünümünde göster ve gönderilmek üzere ekle
                            const stream = await navigator.mediaDevices.getUserMedia(constraints);
                            selfView.srcObject = stream;
                            pc.addTransceiver(stream.getAudioTracks()[0], {direction: 'sendonly'});
                            pc.addTransceiver(stream.getVideoTracks()[0], {
                              direction: 'sendonly',
                              sendEncodings: [
                                {rid: 'q', scaleResolutionDownBy: 4.0},
                                {rid: 'h', scaleResolutionDownBy: 2.0},
                                {rid: 'f'},
                              ]
                            });
                          } catch (err) {
                            console.error(err);
                          }
                        }

                        signaling.onmessage = async ({data: {description, candidate}}) => {
                          try {
                            if (description) {
                              await pc.setRemoteDescription(description);
                              // bir teklif aldıysak, bir yanıtla karşılık vermemiz gerekir
                              if (description.type == 'offer') {
                                await pc.setLocalDescription();
                                signaling.send({description: pc.localDescription});
                              }
                            } else if (candidate) {
                              await pc.addIceCandidate(candidate);
                            }
                          } catch (err) {
                            console.error(err);
                          }
                        };

10.4 Eşler Arası Veri Örneği

Bu örnek, bir RTCDataChannel nesnesinin nasıl oluşturulacağını ve kanalı diğer eşe bağlamak için gerekli olan teklif/yanıt değişiminin nasıl gerçekleştirileceğini gösterir. RTCDataChannel, kullanıcı girdisi için bir input alanı kullanan basit bir sohbet uygulaması bağlamında kullanılır.

const signaling = new SignalingChannel(); // JSON.stringify/parse işlemlerini yönetir
                        const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
                        let pc, channel;

                        // başlatmak için start() çağrılır
                        function start() {
                          pc = new RTCPeerConnection(configuration);

                          // tüm ICE adaylarını diğer eşe gönder
                          pc.onicecandidate = ({candidate}) => signaling.send({candidate});

                          // "negotiationneeded" olayının teklif üretimini tetiklemesine izin ver
                          pc.onnegotiationneeded = async () => {
                            try {
                              await pc.setLocalDescription();
                              // teklifi diğer eşe gönder
                              signaling.send({description: pc.localDescription});
                            } catch (err) {
                              console.error(err);
                            }
                          };

                          // veri kanalını oluştur ve "negotiated" kalıbını kullanarak sohbeti ayarla
                          channel = pc.createDataChannel('chat', {negotiated: true, id: 0});
                          channel.onopen = () => input.disabled = false;
                          channel.onmessage = ({data}) => showChatMessage(data);

                          input.onkeydown = ({key}) => {
                            if (key != 'Enter') return;
                            channel.send(input.value);
                          }
                        }

                        signaling.onmessage = async ({data: {description, candidate}}) => {
                          if (!pc) start();

                          try {
                            if (description) {
                              await pc.setRemoteDescription(description);
                              // bir teklif aldıysak, bir yanıtla karşılık vermemiz gerekir
                              if (description.type == 'offer') {
                                await pc.setLocalDescription();
                                signaling.send({description: pc.localDescription});
                              }
                            } else if (candidate) {
                              await pc.addIceCandidate(candidate);
                            }
                          } catch (err) {
                            console.error(err);
                          }
                        };

10.5 Tarayıcıdan Tarayıcıya Çağrı Akışı

Bu, iki tarayıcı arasında olası bir çağrı akışının bir örneğini gösterir. Yerel medyaya erişim elde etme prosedürünü veya tetiklenen her geri çağrıyı göstermez; bunun yerine yalnızca temel olayları ve mesajları göstermek için sadeleştirilmiştir.

İki tarayıcı arasındaki bir çağrı akışını ayrıntılandıran mesaj sıralama diyagramı
İki tarayıcı arasındaki bir çağrı akışını ayrıntılandıran mesaj sıralama diyagramı

10.6 DTMF Örneği

Örnekler, sender değişkeninin bir RTCRtpSender olduğunu varsayar.

10.6.1 Basit DTMF gönderim

Her ton için 500 ms süreyle "1234" DTMF sinyalini gönderme:

if (sender.dtmf.canInsertDTMF) {
                          const duration = 500;
                          sender.dtmf.insertDTMF('1234', duration);
                        } else {
                          console.log('DTMF işlevi kullanılamıyor');
                        }

10.6.2 DTMF iptal etme

"123" DTMF sinyalini gönder ve "2" gönderildikten sonra iptal et:

async function sendDTMF() {
                          if (sender.dtmf.canInsertDTMF) {
                            sender.dtmf.insertDTMF('123');
                            await new Promise(r => sender.dtmf.ontonechange = e => e.tone == '2' && r());
                            // "2"den sonra herhangi bir ton çalınmaması için arabelleği boşalt
                            sender.dtmf.insertDTMF('');
                          } else {
                            console.log('DTMF işlevi kullanılamıyor');
                          }
                        }

10.6.3 DTMF tuş aydınlatma

"1234" DTMF sinyalini gönder ve ton çalınırken lightKey(key) kullanarak aktif tuşu aydınlat (lightKey("") fonksiyonunun tüm tuşları söndüreceği varsayılır):

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

                        if (sender.dtmf.canInsertDTMF) {
                          const duration = 500; // ms
                          sender.dtmf.insertDTMF(sender.dtmf.toneBuffer + '1234', duration);
                          sender.dtmf.ontonechange = async ({tone}) => {
                            if (!tone) return;
                            lightKey(tone); // çalma başladığında tuşu aydınlat
                            await wait(duration);
                            lightKey(''); // ton süresinden sonra ışığı kapat
                          };
                        } else {
                          console.log('DTMF işlevi kullanılamıyor');
                        }

10.6.4 Ton arabelleğine ekleme

Ton arabelleğine ekleme yapmak her zaman güvenlidir. Bu örnek, ton çalımı başlamadan önce ve çalım sırasında ekleme yapar:

if (sender.dtmf.canInsertDTMF) {
                          sender.dtmf.insertDTMF('123');
                          // çalma başlamadan önce ton arabelleğine daha fazla ton ekle
                          sender.dtmf.insertDTMF(sender.dtmf.toneBuffer + '456');

                          sender.dtmf.ontonechange = ({tone}) => {
                            // çalma başladıktan sonra daha fazla ton ekle
                            if (tone != '1') return;
                            sender.dtmf.insertDTMF(sender.dtmf.toneBuffer + '789');
                          };
                        } else {
                          console.log('DTMF işlevi kullanılamıyor');
                        }

10.6.5 Farklı süreli tonlar

1 saniyelik bir "1" tonu ve ardından 2 saniyelik bir "2" tonu gönder:

if (sender.dtmf.canInsertDTMF) {
                          sender.dtmf.ontonechange = ({tone}) => {
                            if (tone == '1') {
                              sender.dtmf.insertDTMF(sender.dtmf.toneBuffer + '2', 2000);
                            }
                          };
                          sender.dtmf.insertDTMF(sender.dtmf.toneBuffer + '1', 1000);
                        } else {
                          console.log('DTMF işlevi kullanılamıyor');
                        }

10.7 Mükemmel Müzakere Örneği

Mükemmel müzakere, müzakereyi şeffaf bir şekilde yönetmek ve bu asimetrik görevi uygulamanın geri kalanından soyutlamak için önerilen bir kalıptır. Bu kalıp, her zaman bir tarafın teklifçi olmasına kıyasla avantajlar sunar; çünkü uygulamaların, parlamaya (stable durumu dışında gelen bir teklif) yol açma riski olmadan her iki eş bağlantı nesnesi üzerinde aynı anda çalışmasına olanak tanır.

Bu yaklaşım, iki eşe sinyalizasyon çakışmalarını çözmek için farklı roller atar:

  1. Nazik eş (polite): Gelen bir teklifle çakışmayı önlemek için geri alma (rollback) kullanır.
  2. Nazik olmayan eş (impolite): Kendi teklifiyle çakışacaksa gelen teklifi yok sayar.

Birlikte, uygulamanın geri kalanı için sinyalizasyonu kilitlenmeye yol açmayacak şekilde yönetirler. Örnek, atanmış rolü belirten bir polite boolean değişkeni varsayar:

const signaling = new SignalingChannel(); // JSON.stringify/parse işlemlerini yönetir
                        const constraints = {audio: true, video: true};
                        const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
                        const pc = new RTCPeerConnection(configuration);

                        // bağlantıya kamera ve mikrofon eklemek için her iki uçta da
                        // istenilen zamanda start() çağrılabilir
                        async function start() {
                          try {
                            const stream = await navigator.mediaDevices.getUserMedia(constraints);
                            for (const track of stream.getTracks()) {
                              pc.addTrack(track, stream);
                            }
                            selfView.srcObject = stream;
                          } catch (err) {
                            console.error(err);
                          }
                        }

                        pc.ontrack = ({track, streams}) => {
                          // uzak bir iz için medya geldiğinde, bunu uzak video öğesinde göster
                          track.onunmute = () => {
                            // eğer zaten ayarlıysa srcObject'i tekrar ayarlama.
                            if (remoteView.srcObject) return;
                            remoteView.srcObject = streams[0];
                          };
                        };

                        // --- Uygulamanın geri kalanından ayrılmış mükemmel müzakere mantığı ---

                        // yarışları ve hataları önlemek için bazı müzakere durumlarını takip et
                        let makingOffer = false;
                        let ignoreOffer = false;
                        let isSettingRemoteAnswerPending = false;

                        // tüm ICE adaylarını diğer eşe gönder
                        pc.onicecandidate = ({candidate}) => signaling.send({candidate});

                        // "negotiationneeded" olayının teklif üretimini tetiklemesine izin ver
                        pc.onnegotiationneeded = async () => {
                          try {
                            makingOffer = true;
                            await pc.setLocalDescription();
                            signaling.send({description: pc.localDescription});
                          } catch (err) {
                             console.error(err);
                          } finally {
                            makingOffer = false;
                          }
                        };

                        signaling.onmessage = async ({data: {description, candidate}}) => {
                          try {
                            if (description) {
                              // Bir teklif, SRD(answer) işlenmesiyle meşgulken gelebilir.
                              // Bu durumda, teklif işlendiğinde "stable" durumunda olacağız
                              // dolayısıyla onu şimdi İşlemler Zincirimize bağlamak güvenlidir.
                              const readyForOffer =
                                  !makingOffer &&
                                  (pc.signalingState == "stable" || isSettingRemoteAnswerPending);
                              const offerCollision = description.type == "offer" && !readyForOffer;
                              ignoreOffer = !polite && offerCollision;
                              if (ignoreOffer) {
                                return;
                              }
                              isSettingRemoteAnswerPending = description.type == "answer";
                              await pc.setRemoteDescription(description); // SRD gerektiğinde geri alır
                              isSettingRemoteAnswerPending = false;
                              if (description.type == "offer") {
                                await pc.setLocalDescription();
                                signaling.send({description: pc.localDescription});
                              }
                            } else if (candidate) {
                              try {
                                await pc.addIceCandidate(candidate);
                              } catch (err) {
                                if (!ignoreOffer) throw err; // Yok sayılan tekliflere ait adayları bastır
                              }
                            }
                          } catch (err) {
                            console.error(err);
                          }
                        };