Artikulu honetan bideoak websocketen bidez erreproduzitzeko nire saiakerak partekatu nahi ditut hirugarrenen arakatzaileen pluginak erabili gabe, hala nola Adobe Flash Player. Irakurri zer atera zen jakiteko.
Adobe Flash, lehen Macromedia Flash, web arakatzaile batean exekutatzen diren aplikazioak sortzeko plataforma bat da. Media Stream APIa sartu aurretik, ia web-kamera batetik bideoa eta ahotsa erreproduzitzeko plataforma bakarra zen, baita nabigatzailean hainbat hitzaldi eta txatak sortzeko ere. Multimedia-informazioa RTMP (Real Time Messaging Protocol) transmititzeko protokoloa denbora luzez itxita egon zen, eta horrek esan nahi zuen: zure streaming zerbitzua areagotu nahi baduzu, izan nahikoa Adobe-ren softwarea erabiltzeko - Adobe Media Server (AMS).
2012an denbora pixka bat igaro ondoren, Adobek " amore eman eta tu egin zuen " publikoari.
Adobe Flash plataformak 20 urte baino gehiago ditu, eta denbora horretan ahultasun kritiko asko aurkitu dira, laguntza
Nire proiekturako, berehala erabaki nuen nabigatzailean Flash erabilera erabat uztea. Goian arrazoi nagusia adierazi nuen; Flash ere ez da batere onartzen plataforma mugikorretan, eta benetan ez nuen Adobe Flash garatu nahi Windows-en (ardoaren emuladorea) garatzeko. Beraz, bezero bat JavaScript-en idaztera abiatu nintzen. Prototipo bat besterik ez da izango, geroago jakin nuenez streaming-a askoz eraginkorrago egin daitekeela p2p-n oinarrituta, niretzat bakarrik peer - zerbitzaria - parekoak izango dira, baina beste behin gehiago, oraindik prest ez dagoelako.
Hasteko, benetako websockets zerbitzaria behar dugu. Melodia go paketean oinarrituta egin nuen sinpleena:
Zerbitzariaren kodea
package main
import (
"errors"
"github.com/go-chi/chi"
"gopkg.in/olahol/melody.v1"
"log"
"net/http"
"time"
)
func main() {
r := chi.NewRouter()
m := melody.New()
m.Config.MaxMessageSize = 204800
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "public/index.html")
})
r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r)
})
// ΠΡΠΎΠ΄ΠΊΠ°ΡΡΠΈΠΌ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊ
m.HandleMessageBinary(func(s *melody.Session, msg []byte) {
m.BroadcastBinary(msg)
})
log.Println("Starting server...")
http.ListenAndServe(":3000", r)
}
Bezeroan (streaming aldean), lehenik kamera sartu behar duzu. Hau bidez egiten da
Kamera/mikrofonorako sarbidea (baimena) lortzen dugu
Ondoren, getUserMedia()-k Promise bat itzultzen du, eta hari MediaStream objektu bat pasatzen dio - bideo-audio datu-korronte bat. Objektu hau bideo elementuaren src propietateari esleitzen diogu. Kodea:
Emisioaren aldea
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- ΠΠ΄Π΅ΡΡ Π² ΡΡΠΎΠΌ "ΠΎΠΊΠΎΡΠ΅ΡΠΊΠ΅" ΠΊΠ»ΠΈΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer');
// Π΅ΡΠ»ΠΈ Π΄ΠΎΡΡΡΠΏΠ΅Π½ MediaDevices API, ΠΏΡΡΠ°Π΅ΠΌΡΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΠΊΠ°ΠΌΠ΅ΡΠ΅ (ΠΌΠΎΠΆΠ½ΠΎ Π΅ΡΠ΅ ΠΈ ΠΊ ΠΌΠΈΠΊΡΠΎΡΠΎΠ½Ρ)
// getUserMedia Π²Π΅ΡΠ½Π΅Ρ ΠΎΠ±Π΅ΡΠ°Π½ΠΈΠ΅, Π½Π° ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅ΠΌΡΡ ΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊ Π² ΠΊΠΎΠ»Π±Π΅ΠΊΠ΅ Π½Π°ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ Π² video ΠΎΠ±ΡΠ΅ΠΊΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
// Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊ ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΠΊ video ΡΠ΅Π³Ρ, ΡΡΠΎΠ±Ρ ΠΊΠ»ΠΈΠ΅Π½Ρ ΠΌΠΎΠ³ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ ΠΈ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ
video.srcObject = stream;
});
}
</script>
Bideo-korronte bat entxufeen bidez igortzeko, nonbait kodetu behar duzu, tamponatu eta zatika transmititu. Bideo korronte gordina ezin da websocket bidez transmititu. Horra datorkigu laguntza
Bideo korrontea kodetzen dugu, zatitan zatitzen dugu
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- ΠΠ΄Π΅ΡΡ Π² ΡΡΠΎΠΌ "ΠΎΠΊΠΎΡΠ΅ΡΠΊΠ΅" ΠΊΠ»ΠΈΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer');
// Π΅ΡΠ»ΠΈ Π΄ΠΎΡΡΡΠΏΠ΅Π½ MediaDevices API, ΠΏΡΡΠ°Π΅ΠΌΡΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΠΊΠ°ΠΌΠ΅ΡΠ΅ (ΠΌΠΎΠΆΠ½ΠΎ Π΅ΡΠ΅ ΠΈ ΠΊ ΠΌΠΈΠΊΡΠΎΡΠΎΠ½Ρ)
// getUserMedia Π²Π΅ΡΠ½Π΅Ρ ΠΎΠ±Π΅ΡΠ°Π½ΠΈΠ΅, Π½Π° ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅ΠΌΡΡ ΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊ Π² ΠΊΠΎΠ»Π±Π΅ΠΊΠ΅ Π½Π°ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ Π² video ΠΎΠ±ΡΠ΅ΠΊΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
// Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊ ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΠΊ video ΡΠ΅Π³Ρ, ΡΡΠΎΠ±Ρ ΠΊΠ»ΠΈΠ΅Π½Ρ ΠΌΠΎΠ³ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ ΠΈ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ
video.srcObject = s;
var
recorderOptions = {
mimeType: 'video/webm; codecs=vp8' // Π±ΡΠ΄Π΅ΠΌ ΠΊΠΎΠ΄ΠΈΡΠΎΠ²Π°ΡΡ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊ Π² ΡΠΎΡΠΌΠ°Ρ webm ΠΊΠΎΠ΄Π΅ΠΊΠΎΠΌ vp8
},
mediaRecorder = new MediaRecorder(s, recorderOptions ); // ΠΎΠ±ΡΠ΅ΠΊΡ MediaRecorder
mediaRecorder.ondataavailable = function(e) {
if (e.data && e.data.size > 0) {
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΊΡΡΠΎΡΠ΅ΠΊ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊΠ° Π² e.data
}
}
mediaRecorder.start(100); // Π΄Π΅Π»ΠΈΡ ΠΏΠΎΡΠΎΠΊ Π½Π° ΠΊΡΡΠΎΡΠΊΠΈ ΠΏΠΎ 100 ΠΌΡ ΠΊΠ°ΠΆΠ΄ΡΠΉ
});
}
</script>
Orain gehi dezagun transmisioa websocket bidez. Harrigarria bada ere, horretarako behar duzun guztia objektu bat da
Bideo korrontea zerbitzariari transmititzen diogu
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- ΠΠ΄Π΅ΡΡ Π² ΡΡΠΎΠΌ "ΠΎΠΊΠΎΡΠ΅ΡΠΊΠ΅" ΠΊΠ»ΠΈΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer');
// Π΅ΡΠ»ΠΈ Π΄ΠΎΡΡΡΠΏΠ΅Π½ MediaDevices API, ΠΏΡΡΠ°Π΅ΠΌΡΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΠΊΠ°ΠΌΠ΅ΡΠ΅ (ΠΌΠΎΠΆΠ½ΠΎ Π΅ΡΠ΅ ΠΈ ΠΊ ΠΌΠΈΠΊΡΠΎΡΠΎΠ½Ρ)
// getUserMedia Π²Π΅ΡΠ½Π΅Ρ ΠΎΠ±Π΅ΡΠ°Π½ΠΈΠ΅, Π½Π° ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅ΠΌΡΡ ΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊ Π² ΠΊΠΎΠ»Π±Π΅ΠΊΠ΅ Π½Π°ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ Π² video ΠΎΠ±ΡΠ΅ΠΊΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
// Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊ ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΠΊ video ΡΠ΅Π³Ρ, ΡΡΠΎΠ±Ρ ΠΊΠ»ΠΈΠ΅Π½Ρ ΠΌΠΎΠ³ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ ΠΈ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ
video.srcObject = s;
var
recorderOptions = {
mimeType: 'video/webm; codecs=vp8' // Π±ΡΠ΄Π΅ΠΌ ΠΊΠΎΠ΄ΠΈΡΠΎΠ²Π°ΡΡ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊ Π² ΡΠΎΡΠΌΠ°Ρ webm ΠΊΠΎΠ΄Π΅ΠΊΠΎΠΌ vp8
},
mediaRecorder = new MediaRecorder(s, recorderOptions ), // ΠΎΠ±ΡΠ΅ΠΊΡ MediaRecorder
socket = new WebSocket('ws://127.0.0.1:3000/ws');
mediaRecorder.ondataavailable = function(e) {
if (e.data && e.data.size > 0) {
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΊΡΡΠΎΡΠ΅ΠΊ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊΠ° Π² e.data
socket.send(e.data);
}
}
mediaRecorder.start(100); // Π΄Π΅Π»ΠΈΡ ΠΏΠΎΡΠΎΠΊ Π½Π° ΠΊΡΡΠΎΡΠΊΠΈ ΠΏΠΎ 100 ΠΌΡ ΠΊΠ°ΠΆΠ΄ΡΠΉ
}).catch(function (err) { console.log(err); });
}
</script>
Emisio aldea prest dago! Orain saia gaitezen bideo-korronte bat jasotzen eta bezeroari bistaratzen. Zer behar dugu horretarako? Lehenik eta behin, noski, socket konexioa. WebSocket objektuari "entzule" bat eransten diogu eta "mezu" gertaerara harpidetzen gara. Datu bitarren bat jasota, gure zerbitzariak harpidedunei, hau da, bezeroei igortzen die. Kasu honetan, "mezuaren" gertaeraren "entzulearekin" lotutako dei-itzulera funtzioa abiarazten da bezeroan; objektua bera funtzioaren argumentura pasatzen da - vp8-k kodetutako bideo-korrontearen zati bat.
Bideo korrontea onartzen dugu
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- ΠΠ΄Π΅ΡΡ Π² ΡΡΠΎΠΌ "ΠΎΠΊΠΎΡΠ΅ΡΠΊΠ΅" ΠΊΠ»ΠΈΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer'),
socket = new WebSocket('ws://127.0.0.1:3000/ws'),
arrayOfBlobs = [];
socket.addEventListener('message', function (event) {
// "ΠΊΠ»Π°Π΄Π΅ΠΌ" ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ ΠΊΡΡΠΎΡΠ΅ΠΊ Π² ΠΌΠ°ΡΡΠΈΠ²
arrayOfBlobs.push(event.data);
// Π·Π΄Π΅ΡΡ Π±ΡΠ΄Π΅ΠΌ ΡΠΈΡΠ°ΡΡ ΠΊΡΡΠΎΡΠΊΠΈ
readChunk();
});
</script>
Denbora luzez saiatu nintzen ulertzen zergatik ezinezkoa den jasotako piezak bideo-elementura berehala bidaltzea erreproduzitzeko, baina hori ezin dela egin ondorioztatu zen, noski, lehenik pieza buffer berezi batean jarri behar duzu. bideo-elementua, eta orduan bakarrik hasiko da bideo-korrontea erreproduzitzen. Horretarako beharko duzu
MediaSource-k multimedia-erreprodukzio-objektuaren eta multimedia-korronte honen iturriaren arteko bitartekari moduko bat egiten du. MediaSource objektuak buffer konektagarri bat dauka bideo/audio korrontearen iturrirako. Ezaugarri bat da buffer-ak Uint8 datuak soilik eduki ditzakeela, beraz, FileReader bat beharko duzu buffer hori sortzeko. Begiratu kodea eta argiago geratuko da:
Bideo korrontea erreproduzitzen
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- ΠΠ΄Π΅ΡΡ Π² ΡΡΠΎΠΌ "ΠΎΠΊΠΎΡΠ΅ΡΠΊΠ΅" ΠΊΠ»ΠΈΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ Π²ΠΈΠ΄Π΅ΡΡ ΡΠ΅Π±Ρ -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer'),
socket = new WebSocket('ws://127.0.0.1:3000/ws'),
mediaSource = new MediaSource(), // ΠΎΠ±ΡΠ΅ΠΊΡ MediaSource
vid2url = URL.createObjectURL(mediaSource), // ΡΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠ±ΡΠ΅ΠΊΡ URL Π΄Π»Ρ ΡΠ²ΡΠ·ΡΠ²Π°Π½ΠΈΡ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊΠ° Ρ ΠΏΡΠΎΠΈΠ³ΡΡΠ²Π°ΡΠ΅Π»Π΅ΠΌ
arrayOfBlobs = [],
sourceBuffer = null; // Π±ΡΡΠ΅Ρ, ΠΏΠΎΠΊΠ° Π½ΡΠ»Ρ-ΠΎΠ±ΡΠ΅ΠΊΡ
socket.addEventListener('message', function (event) {
// "ΠΊΠ»Π°Π΄Π΅ΠΌ" ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ ΠΊΡΡΠΎΡΠ΅ΠΊ Π² ΠΌΠ°ΡΡΠΈΠ²
arrayOfBlobs.push(event.data);
// Π·Π΄Π΅ΡΡ Π±ΡΠ΄Π΅ΠΌ ΡΠΈΡΠ°ΡΡ ΠΊΡΡΠΎΡΠΊΠΈ
readChunk();
});
// ΠΊΠ°ΠΊ ΡΠΎΠ»ΡΠΊΠΎ MediaSource Π±ΡΠ΄Π΅Ρ ΠΎΠΏΠΎΠ²Π΅ΡΠ΅Π½ , ΡΡΠΎ ΠΈΡΡΠΎΡΠ½ΠΈΠΊ Π³ΠΎΡΠΎΠ² ΠΎΡΠ΄Π°Π²Π°ΡΡ ΠΊΡΡΠΎΡΠΊΠΈ
// Π²ΠΈΠ΄Π΅ΠΎ/Π°ΡΠ΄ΠΈΠΎ ΠΏΠΎΡΠΎΠΊΠ°
// ΡΠΎΠ·Π΄Π°Π΅ΠΌ Π±ΡΡΠ΅Ρ , ΡΠ»Π΅Π΄ΡΠ΅Ρ ΠΎΠ±ΡΠ°ΡΠΈΡΡ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΡΡΠΎ Π±ΡΡΠ΅Ρ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π·Π½Π°ΡΡ Π² ΠΊΠ°ΠΊΠΎΠΌ ΡΠΎΡΠΌΠ°ΡΠ΅
// ΠΊΠ°ΠΊΠΈΠΌ ΠΊΠΎΠ΄Π΅ΠΊΠΎΠΌ Π±ΡΠ» Π·Π°ΠΊΠΎΠ΄ΠΈΡΠΎΠ²Π°Π½ ΠΏΠΎΡΠΎΠΊ, ΡΡΠΎΠ±Ρ ΡΠ΅ΠΌ ΠΆΠ΅ ΡΠΏΠΎΡΠΎΠ±ΠΎΠΌ ΠΏΡΠΎΡΠΈΡΠ°ΡΡ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊ
mediaSource.addEventListener('sourceopen', function() {
var mediaSource = this;
sourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs="vp8"");
});
function readChunk() {
var reader = new FileReader();
reader.onload = function(e) {
// ΠΊΠ°ΠΊ ΡΠΎΠ»ΡΠΊΠΎ FileReader Π±ΡΠ΄Π΅Ρ Π³ΠΎΡΠΎΠ², ΠΈ Π·Π°Π³ΡΡΠ·ΠΈΡ ΡΠ΅Π±Π΅ ΠΊΡΡΠΎΡΠ΅ΠΊ Π²ΠΈΠ΄Π΅ΠΎΠΏΠΎΡΠΎΠΊΠ°
// ΠΌΡ "ΠΏΡΠΈΡΠ΅ΠΏΠ»ΡΠ΅ΠΌ" ΠΏΠ΅ΡΠ΅ΠΊΠΎΠ΄ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ Π² Uint8Array (Π±ΡΠ» Blob) ΠΊΡΡΠΎΡΠ΅ΠΊ Π² Π±ΡΡΠ΅Ρ, ΡΠ²ΡΠ·Π°Π½Π½ΡΠΉ
// Ρ ΠΏΡΠΎΠΈΠ³ΡΡΠ²Π°ΡΠ΅Π»Π΅ΠΌ, ΠΈ ΠΏΡΠΎΠΈΠ³ΡΡΠ²Π°ΡΠ΅Π»Ρ Π½Π°ΡΠΈΠ½Π°Π΅Ρ Π²ΠΎΡΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΡ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΡΠΉ ΠΊΡΡΠΎΡΠ΅ΠΊ Π²ΠΈΠ΄Π΅ΠΎ/Π°ΡΠ΄ΠΈΠΎ
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
reader.onload = null;
}
reader.readAsArrayBuffer(arrayOfBlobs.shift());
}
</script>
Streaming zerbitzuaren prototipoa prest dago. Desabantaila nagusia da bideo-erreprodukzioa 100 ms-ko atzerapena izango duela transmisio-aldearekiko; hori geuk ezartzen dugu bideo-jarioa zatitzean zerbitzarira transmititu aurretik. Gainera, ordenagailu eramangarrian egiaztatu nuenean, igorlearen eta hartzaileen arteko desfasea pixkanaka-pixkanaka pilatzen joan zen, hori argi ikusten zen. Desabantaila hori gainditzeko bideak bilatzen hasi nintzen, eta... topo egin nuen
Iturria: www.habr.com