ΠΠΎ ΠΎΠ²Π°Π° ΡΡΠ°ΡΠΈΡΠ° ΡΠ°ΠΊΠ°ΠΌ Π΄Π° Π³ΠΈ ΡΠΏΠΎΠ΄Π΅Π»Π°ΠΌ ΠΌΠΎΠΈΡΠ΅ ΠΎΠ±ΠΈΠ΄ΠΈ Π·Π° ΠΏΡΠ΅Π½ΠΎΡ Π½Π° Π²ΠΈΠ΄Π΅ΠΎ ΠΏΡΠ΅ΠΊΡ Π²Π΅Π±-ΡΠΎΠΊΠ΅ΡΠΈ Π±Π΅Π· ΠΊΠΎΡΠΈΡΡΠ΅ΡΠ΅ Π½Π° ΠΏΡΠΈΠΊΠ»ΡΡΠΎΡΠΈ Π·Π° ΠΏΡΠ΅Π»ΠΈΡΡΡΠ²Π°ΡΠΈ ΠΎΠ΄ ΡΡΠ΅ΡΠΈ ΡΡΡΠ°Π½ΠΈ, ΠΊΠ°ΠΊΠΎ ΡΡΠΎ Π΅ Adobe Flash Player. ΠΡΠΎΡΠΈΡΠ°ΡΡΠ΅ Π·Π° Π΄Π° Π΄ΠΎΠ·Π½Π°Π΅ΡΠ΅ ΡΡΠΎ ΠΈΠ·Π»Π΅Π·Π΅ ΠΎΠ΄ ΡΠΎΠ°.
Adobe Flash, ΠΏΠΎΡΠ°Π½ΠΎ Macromedia Flash, Π΅ ΠΏΠ»Π°ΡΡΠΎΡΠΌΠ° Π·Π° ΠΊΡΠ΅ΠΈΡΠ°ΡΠ΅ Π°ΠΏΠ»ΠΈΠΊΠ°ΡΠΈΠΈ ΠΊΠΎΠΈ ΡΠ°Π±ΠΎΡΠ°Ρ Π²ΠΎ Π²Π΅Π±-ΠΏΡΠ΅Π»ΠΈΡΡΡΠ²Π°Ρ. ΠΡΠ΅Π΄ Π²ΠΎΠ²Π΅Π΄ΡΠ²Π°ΡΠ΅ΡΠΎ Π½Π° Media Stream API, ΡΠΎΡ Π±Π΅ΡΠ΅ ΠΏΡΠ°ΠΊΡΠΈΡΠ½ΠΎ Π΅Π΄ΠΈΠ½ΡΡΠ²Π΅Π½Π°ΡΠ° ΠΏΠ»Π°ΡΡΠΎΡΠΌΠ° Π·Π° ΠΏΡΠΎΡΠ»Π΅Π΄ΡΠ²Π°ΡΠ΅ Π²ΠΈΠ΄Π΅ΠΎ ΠΈ Π³Π»Π°Ρ ΠΎΠ΄ Π²Π΅Π±-ΠΊΠ°ΠΌΠ΅ΡΠ°, ΠΊΠ°ΠΊΠΎ ΠΈ Π·Π° ΡΠΎΠ·Π΄Π°Π²Π°ΡΠ΅ ΡΠ°Π·Π»ΠΈΡΠ½ΠΈ Π²ΠΈΠ΄ΠΎΠ²ΠΈ ΠΊΠΎΠ½ΡΠ΅ΡΠ΅Π½ΡΠΈΠΈ ΠΈ ΡΠ°Π·Π³ΠΎΠ²ΠΎΡΠΈ Π²ΠΎ ΠΏΡΠ΅Π»ΠΈΡΡΡΠ²Π°ΡΠΎΡ. ΠΡΠΎΡΠΎΠΊΠΎΠ»ΠΎΡ Π·Π° ΠΏΡΠ΅Π½ΠΎΡ Π½Π° ΠΌΠ΅Π΄ΠΈΡΠΌΡΠΊΠΈ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ RTMP (Real Time Messaging Protocol) Π²ΡΡΡΠ½ΠΎΡΡ Π±Π΅ΡΠ΅ Π·Π°ΡΠ²ΠΎΡΠ΅Π½ Π΄ΠΎΠ»Π³ΠΎ Π²ΡΠ΅ΠΌΠ΅, ΡΡΠΎ Π·Π½Π°ΡΠ΅ΡΠ΅: Π°ΠΊΠΎ ΡΠ°ΠΊΠ°ΡΠ΅ Π΄Π° ΡΠ° Π·Π°ΡΠ°ΠΊΠ½Π΅ΡΠ΅ Π²Π°ΡΠ°ΡΠ° ΡΡΠ»ΡΠ³Π° Π·Π° ΡΡΡΠΈΠΌΠΈΠ½Π³, Π±ΠΈΠ΄Π΅ΡΠ΅ Π΄ΠΎΠ²ΠΎΠ»Π½ΠΎ ΡΡΠ±Π΅Π·Π½ΠΈ Π΄Π° ΠΊΠΎΡΠΈΡΡΠΈΡΠ΅ ΡΠΎΡΡΠ²Π΅Ρ ΠΎΠ΄ ΡΠ°ΠΌΠΈΡΠ΅ Adobe - Adobe Media Server (AMS).
ΠΠΎ Π½Π΅ΠΊΠΎΠ΅ Π²ΡΠ΅ΠΌΠ΅ Π²ΠΎ 2012 Π³ΠΎΠ΄ΠΈΠ½Π°, Adobe βΡΠ΅ ΠΎΡΠΊΠ°ΠΆΠ° ΠΈ Π³ΠΎ ΠΈΡΠΏΠ»ΡΠΊΠ°β Π½Π° ΡΠ°Π²Π½ΠΎΡΡΠ°.
ΠΠ»Π°ΡΡΠΎΡΠΌΠ°ΡΠ° Adobe Flash Π΅ ΡΡΠ°ΡΠ° ΠΏΠΎΠ²Π΅ΡΠ΅ ΠΎΠ΄ 20 Π³ΠΎΠ΄ΠΈΠ½ΠΈ, Π·Π° ΡΠΎΠ° Π²ΡΠ΅ΠΌΠ΅ ΡΠ΅ ΠΎΡΠΊΡΠΈΠ΅Π½ΠΈ ΠΌΠ½ΠΎΠ³Ρ ΠΊΡΠΈΡΠΈΡΠ½ΠΈ ΠΏΡΠΎΠΏΡΡΡΠΈ, ΠΏΠΎΠ΄Π΄ΡΡΠΊΠ°
ΠΠ° ΠΌΠΎΡΠΎΡ ΠΏΡΠΎΠ΅ΠΊΡ, Π²Π΅Π΄Π½Π°Ρ ΡΠ΅ΡΠΈΠ² ΡΠ΅Π»ΠΎΡΠ½ΠΎ Π΄Π° ΡΠ° Π½Π°ΠΏΡΡΡΠ°ΠΌ ΡΠΏΠΎΡΡΠ΅Π±Π°ΡΠ° Π½Π° Flash Π²ΠΎ ΠΏΡΠ΅Π»ΠΈΡΡΡΠ²Π°ΡΠΎΡ. ΠΠ° Π½Π°Π²Π΅Π΄ΠΎΠ² Π³Π»Π°Π²Π½Π°ΡΠ° ΠΏΡΠΈΡΠΈΠ½Π° ΠΏΠΎΠ³ΠΎΡΠ΅; Flash ΠΈΡΡΠΎ ΡΠ°ΠΊΠ° Π²ΠΎΠΎΠΏΡΡΠΎ Π½Π΅ Π΅ ΠΏΠΎΠ΄Π΄ΡΠΆΠ°Π½ Π½Π° ΠΌΠΎΠ±ΠΈΠ»Π½ΠΈΡΠ΅ ΠΏΠ»Π°ΡΡΠΎΡΠΌΠΈ ΠΈ Π½Π°Π²ΠΈΡΡΠΈΠ½Π° Π½Π΅ ΡΠ°ΠΊΠ°Π² Π΄Π° Π³ΠΎ ΡΠ°ΡΠΏΠΎΡΠ΅Π΄Π°ΠΌ Adobe Flash Π·Π° ΡΠ°Π·Π²ΠΎΡ Π½Π° Windows (Π΅ΠΌΡΠ»Π°ΡΠΎΡ Π·Π° Π²ΠΈΠ½ΠΎ). Π’Π°ΠΊΠ°, ΡΡΠ³Π½Π°Π² Π΄Π° Π½Π°ΠΏΠΈΡΠ°ΠΌ ΠΊΠ»ΠΈΠ΅Π½Ρ Π²ΠΎ JavaScript. ΠΠ²Π° ΡΠ΅ Π±ΠΈΠ΄Π΅ ΡΠ°ΠΌΠΎ ΠΏΡΠΎΡΠΎΡΠΈΠΏ, Π±ΠΈΠ΄Π΅ΡΡΠΈ ΠΏΠΎΠ΄ΠΎΡΠ½Π° Π΄ΠΎΠ·Π½Π°Π² Π΄Π΅ΠΊΠ° ΡΡΡΠΈΠΌΠΈΠ½Π³ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ Π½Π°ΠΏΡΠ°Π²ΠΈ ΠΌΠ½ΠΎΠ³Ρ ΠΏΠΎΠ΅ΡΠΈΠΊΠ°ΡΠ½ΠΎ Π²ΡΠ· ΠΎΡΠ½ΠΎΠ²Π° Π½Π° p2p, ΡΠ°ΠΌΠΎ Π·Π° ΠΌΠ΅Π½Π΅ ΡΠ΅ Π±ΠΈΠ΄Π΅ peer - server - peers, Π½ΠΎ ΠΏΠΎΠ²Π΅ΡΠ΅ Π·Π° ΡΠΎΠ° Π΄ΡΡΠ³ ΠΏΠ°Ρ, Π±ΠΈΠ΄Π΅ΡΡΠΈ ΡΓ¨ ΡΡΡΠ΅ Π½Π΅ Π΅ Π³ΠΎΡΠΎΠ².
ΠΠ° Π΄Π° Π·Π°ΠΏΠΎΡΠ½Π΅ΠΌΠ΅, Π½ΠΈ ΡΡΠ΅Π±Π° Π²ΠΈΡΡΠΈΠ½ΡΠΊΠΈΠΎΡ Π²Π΅Π±-ΡΠΎΠΊΠ΅Ρ ΡΠ΅ΡΠ²Π΅Ρ. ΠΠ°Ρ Π³ΠΎ Π½Π°ΠΏΡΠ°Π²ΠΈΠ² Π½Π°ΡΠ΅Π΄Π½ΠΎΡΡΠ°Π²Π½ΠΈΠΎΡ Π²ΡΠ· ΠΎΡΠ½ΠΎΠ²Π° Π½Π° ΠΏΠ°ΠΊΠ΅ΡΠΎΡ melody go:
Π‘Π΅ΡΠ²Π΅ΡΡΠΊΠΈ ΠΊΠΎΠ΄
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)
}
ΠΠ° ΠΊΠ»ΠΈΠ΅Π½ΡΠΎΡ (ΡΡΡΠ°Π½Π°ΡΠ° Π½Π° ΡΡΡΠΈΠΌΠΈΠ½Π³), ΠΏΡΠ²ΠΎ ΡΡΠ΅Π±Π° Π΄Π° ΠΏΡΠΈΡΡΠ°ΠΏΠΈΡΠ΅ Π΄ΠΎ ΠΊΠ°ΠΌΠ΅ΡΠ°ΡΠ°. ΠΠ²Π° ΡΠ΅ ΠΏΡΠ°Π²ΠΈ ΠΏΡΠ΅ΠΊΡ
ΠΠΎΠ±ΠΈΠ²Π°ΠΌΠ΅ ΠΏΡΠΈΡΡΠ°ΠΏ (Π΄ΠΎΠ·Π²ΠΎΠ»Π°) Π΄ΠΎ ΠΊΠ°ΠΌΠ΅ΡΠ°ΡΠ°/ΠΌΠΈΠΊΡΠΎΡΠΎΠ½ΠΎΡ ΠΏΡΠ΅ΠΊΡ
Π‘Π»Π΅Π΄Π½ΠΎ, getUserMedia() Π²ΡΠ°ΡΠ° Promise, Π½Π° ΠΊΠΎΠ΅ ΠΌΡ ΠΏΡΠ΅Π΄Π°Π²Π° ΠΎΠ±ΡΠ΅ΠΊΡ MediaStream - ΠΏΠΎΡΠΎΠΊ ΠΎΠ΄ Π²ΠΈΠ΄Π΅ΠΎ-Π°ΡΠ΄ΠΈΠΎ ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ. ΠΠ²ΠΎΡ ΠΎΠ±ΡΠ΅ΠΊΡ Π³ΠΎ Π΄ΠΎΠ΄Π΅Π»ΡΠ²Π°ΠΌΠ΅ Π½Π° ΡΠ²ΠΎΡΡΡΠ²ΠΎΡΠΎ src Π½Π° Π²ΠΈΠ΄Π΅ΠΎ Π΅Π»Π΅ΠΌΠ΅Π½ΡΠΎΡ. ΠΠΎΠ΄:
Π Π°Π΄ΠΈΠΎΠ΄ΠΈΡΡΠ·Π½Π° ΡΡΡΠ°Π½Π°
<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>
ΠΠ° Π΄Π° Π΅ΠΌΠΈΡΡΠ²Π°ΡΠ΅ Π²ΠΈΠ΄Π΅ΠΎ ΡΡΡΠΈΠΌ ΠΏΡΠ΅ΠΊΡ ΠΏΡΠΈΠΊΠ»ΡΡΠΎΡΠΈΡΠ΅, ΡΡΠ΅Π±Π° Π΄Π° Π³ΠΎ ΡΠΈΡΡΠΈΡΠ°ΡΠ΅ Π½Π΅ΠΊΠ°Π΄Π΅, Π΄Π° Π³ΠΎ ΡΠ°ΠΌΠΏΠΎΠ½ΠΈΡΠ°ΡΠ΅ ΠΈ Π΄Π° Π³ΠΎ ΠΏΡΠ΅Π½Π΅ΡΠ΅ΡΠ΅ Π½Π° Π΄Π΅Π»ΠΎΠ²ΠΈ. ΠΠ΅ΠΎΠ±ΡΠ°Π±ΠΎΡΠ΅Π½ΠΈΠΎΡ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊ Π½Π΅ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΠ΅ ΠΏΡΠ΅ΠΊΡ Π²Π΅Π±-ΡΠΎΠΊΠ΅ΡΠΈ. Π’ΡΠΊΠ° Π½ΠΈ Π΄ΠΎΠ°ΡΠ° Π½Π° ΠΏΠΎΠΌΠΎΡ
ΠΠΈΠ΅ Π³ΠΎ ΠΊΠΎΠ΄ΠΈΡΠ°ΠΌΠ΅ Π²ΠΈΠ΄Π΅ΠΎ-ΡΡΡΠΈΠΌΠΎΡ, Π³ΠΎ ΡΠ°ΡΠΏΠ°ΡΠ°ΠΌΠ΅ Π½Π° ΠΏΠ°ΡΡΠΈΡΠ°
<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>
Π‘Π΅Π³Π° Π΄Π° Π΄ΠΎΠ΄Π°Π΄Π΅ΠΌΠ΅ ΠΏΡΠ΅Π½ΠΎΡ ΠΏΡΠ΅ΠΊΡ Π²Π΅Π±-ΡΠΎΠΊΠ΅ΡΠΈ. ΠΠ·Π½Π΅Π½Π°Π΄ΡΠ²Π°ΡΠΊΠΈ, ΡΠ΅ ΡΡΠΎ Π²ΠΈ ΡΡΠ΅Π±Π° Π·Π° ΠΎΠ²Π° Π΅ ΠΎΠ±ΡΠ΅ΠΊΡ
ΠΠΈΠ΅ Π³ΠΎ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°ΠΌΠ΅ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊΠΎΡ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠΎΡ
<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>
Π‘ΡΡΠ°Π½Π°ΡΠ° Π½Π° Π΅ΠΌΠΈΡΡΠ²Π°ΡΠ΅ Π΅ ΠΏΠΎΠ΄Π³ΠΎΡΠ²Π΅Π½Π°! Π‘Π΅Π³Π° Π΄Π° ΡΠ΅ ΠΎΠ±ΠΈΠ΄Π΅ΠΌΠ΅ Π΄Π° ΠΏΡΠΈΠΌΠΈΠΌΠ΅ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊ ΠΈ Π΄Π° Π³ΠΎ ΠΏΡΠΈΠΊΠ°ΠΆΠ΅ΠΌΠ΅ Π½Π° ΠΊΠ»ΠΈΠ΅Π½ΡΠΎΡ. Π¨ΡΠΎ Π½ΠΈ ΡΡΠ΅Π±Π° Π·Π° ΠΎΠ²Π°? ΠΡΠ²ΠΎ, ΡΠ΅ ΡΠ°Π·Π±ΠΈΡΠ°, ΠΏΡΠΈΠΊΠ»ΡΡΠΎΠΊΠΎΡ Π·Π° ΡΡΠ΅ΠΊΠ΅Ρ. ΠΡΠΈΠΊΠ°ΡΡΠ²Π°ΠΌΠ΅ βΡΠ»ΡΡΠ°ΡΠ΅Π»β Π½Π° ΠΎΠ±ΡΠ΅ΠΊΡΠΎΡ WebSocket ΠΈ ΡΠ΅ ΠΏΡΠ΅ΡΠΏΠ»Π°ΡΠΈΠΌΠ΅ Π½Π° Π½Π°ΡΡΠ°Π½ΠΎΡ βΠΏΠΎΡΠ°ΠΊΠ°β. ΠΡΠΊΠ°ΠΊΠΎ Π΄ΠΎΠ±ΠΈΠ²ΠΌΠ΅ Π΄Π΅Π» ΠΎΠ΄ Π±ΠΈΠ½Π°ΡΠ½ΠΈ ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ, Π½Π°ΡΠΈΠΎΡ ΡΠ΅ΡΠ²Π΅Ρ Π³ΠΈ Π΅ΠΌΠΈΡΡΠ²Π° Π½Π° ΠΏΡΠ΅ΡΠΏΠ»Π°ΡΠ½ΠΈΡΠΈ, ΠΎΠ΄Π½ΠΎΡΠ½ΠΎ ΠΊΠ»ΠΈΠ΅Π½ΡΠΈ. ΠΠΎ ΠΎΠ²ΠΎΡ ΡΠ»ΡΡΠ°Ρ, ΡΡΠ½ΠΊΡΠΈΡΠ°ΡΠ° Π·Π° ΠΏΠΎΠ²ΡΠ°ΡΠ΅Π½ ΠΏΠΎΠ²ΠΈΠΊ ΠΏΠΎΠ²ΡΠ·Π°Π½Π° ΡΠΎ βΡΠ»ΡΡΠ°ΡΠ΅Π»ΠΎΡβ Π½Π° Π½Π°ΡΡΠ°Π½ΠΎΡ βΠΏΠΎΡΠ°ΠΊΠ°β ΡΠ΅ Π°ΠΊΡΠΈΠ²ΠΈΡΠ° Π½Π° ΠΊΠ»ΠΈΠ΅Π½ΡΠΎΡ; ΡΠ°ΠΌΠΈΠΎΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π° Π²ΠΎ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΡ Π½Π° ΡΡΠ½ΠΊΡΠΈΡΠ°ΡΠ° - Π΄Π΅Π» ΠΎΠ΄ Π²ΠΈΠ΄Π΅ΠΎ-ΡΡΡΠΈΠΌΠΎΡ ΠΊΠΎΠ΄ΠΈΡΠ°Π½ ΠΎΠ΄ vp8.
ΠΠΈΠ΅ ΠΏΡΠΈΡΠ°ΡΠ°ΠΌΠ΅ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΡΠ΅Π½ΠΎΡ
<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>
ΠΠΎΠ»Π³ΠΎ Π²ΡΠ΅ΠΌΠ΅ ΡΠ΅ ΠΎΠ±ΠΈΠ΄ΡΠ²Π°Π² Π΄Π° ΡΠ°Π·Π±Π΅ΡΠ°ΠΌ Π·ΠΎΡΡΠΎ Π΅ Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ Π²Π΅Π΄Π½Π°Ρ Π΄Π° ΡΠ΅ ΠΈΡΠΏΡΠ°ΡΠ°Ρ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΡΠ΅ ΠΏΠ°ΡΡΠΈΡΠ° Π½Π° Π²ΠΈΠ΄Π΅ΠΎ Π΅Π»Π΅ΠΌΠ΅Π½ΡΠΎΡ Π·Π° ΡΠ΅ΠΏΡΠΎΠ΄ΡΠΊΡΠΈΡΠ°, Π½ΠΎ ΡΠ΅ ΠΏΠΎΠΊΠ°ΠΆΠ° Π΄Π΅ΠΊΠ° ΡΠΎΠ° Π½Π΅ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ Π½Π°ΠΏΡΠ°Π²ΠΈ, ΡΠ΅ ΡΠ°Π·Π±ΠΈΡΠ°, ΠΏΡΠ²ΠΎ ΠΌΠΎΡΠ° Π΄Π° Π³ΠΎ ΡΡΠ°Π²ΠΈΡΠ΅ ΠΏΠ°ΡΡΠ΅ΡΠΎ Π²ΠΎ ΠΏΠΎΡΠ΅Π±Π΅Π½ ΡΠ°ΠΌΠΏΠΎΠ½ Π²ΡΠ·Π°Π½ Π·Π° Π²ΠΈΠ΄Π΅ΠΎ Π΅Π»Π΅ΠΌΠ΅Π½ΡΠΎΡ ΠΈ Π΄ΡΡΠΈ ΡΠΎΠ³Π°Ρ ΡΠ΅ ΠΏΠΎΡΠ½Π΅ Π΄Π° Π³ΠΎ ΡΠ΅ΠΏΡΠΎΠ΄ΡΡΠΈΡΠ° Π²ΠΈΠ΄Π΅ΠΎ-ΡΡΡΠΈΠΌΠΎΡ. ΠΠ° ΠΎΠ²Π° ΡΠ΅ Π²ΠΈ ΡΡΠ΅Π±Π°
MediaSource Π΄Π΅Π»ΡΠ²Π° ΠΊΠ°ΠΊΠΎ Π΅Π΄Π΅Π½ Π²ΠΈΠ΄ ΠΏΠΎΡΡΠ΅Π΄Π½ΠΈΠΊ ΠΏΠΎΠΌΠ΅ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΡ Π·Π° ΡΠ΅ΠΏΡΠΎΠ΄ΡΠΊΡΠΈΡΠ° Π½Π° ΠΌΠ΅Π΄ΠΈΡΠΌΠΈΡΠ΅ ΠΈ ΠΈΠ·Π²ΠΎΡΠΎΡ Π½Π° ΠΎΠ²ΠΎΡ ΠΌΠ΅Π΄ΠΈΡΠΌΡΠΊΠΈ ΠΏΠΎΡΠΎΠΊ. ΠΠ±ΡΠ΅ΠΊΡΠΎΡ MediaSource ΡΠΎΠ΄ΡΠΆΠΈ ΠΏΡΠΈΠΊΠ»ΡΡΠ΅Π½ Π±Π°ΡΠ΅Ρ Π·Π° ΠΈΠ·Π²ΠΎΡΠΎΡ Π½Π° Π²ΠΈΠ΄Π΅ΠΎ/Π°ΡΠ΄ΠΈΠΎ ΠΏΠΎΡΠΎΠΊΠΎΡ. ΠΠ΄Π½Π° ΠΊΠ°ΡΠ°ΠΊΡΠ΅ΡΠΈΡΡΠΈΠΊΠ° Π΅ Π΄Π΅ΠΊΠ° Π±Π°ΡΠ΅ΡΠΎΡ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠΎΠ΄ΡΠΆΠΈ ΡΠ°ΠΌΠΎ ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ Uint8, ΡΠ°ΠΊΠ° ΡΡΠΎ ΡΠ΅ Π²ΠΈ ΡΡΠ΅Π±Π° FileReader Π·Π° Π΄Π° ΠΊΡΠ΅ΠΈΡΠ°ΡΠ΅ ΡΠ°ΠΊΠΎΠ² Π±Π°ΡΠ΅Ρ. ΠΠΎΠ³Π»Π΅Π΄Π½Π΅ΡΠ΅ Π³ΠΎ ΠΊΠΎΠ΄ΠΎΡ ΠΈ ΡΠ΅ Π²ΠΈ ΡΡΠ°Π½Π΅ ΠΏΠΎΡΠ°ΡΠ½ΠΎ:
Π‘Π΅ ΡΠ΅ΠΏΡΠΎΠ΄ΡΡΠΈΡΠ° Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎΡΠΎΠΊΠΎΡ
<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>
ΠΡΠΎΡΠΎΡΠΈΠΏΠΎΡ Π½Π° ΡΡΠ»ΡΠ³Π°ΡΠ° Π·Π° ΡΡΡΠΈΠΌΠΈΠ½Π³ Π΅ ΠΏΠΎΠ΄Π³ΠΎΡΠ²Π΅Π½. ΠΠ»Π°Π²Π½ΠΈΠΎΡ Π½Π΅Π΄ΠΎΡΡΠ°ΡΠΎΠΊ Π΅ ΡΡΠΎ ΡΠ΅ΠΏΡΠΎΠ΄ΡΠΊΡΠΈΡΠ°ΡΠ° Π½Π° Π²ΠΈΠ΄Π΅ΠΎΡΠΎ ΡΠ΅ Π·Π°ΠΎΡΡΠ°Π½ΡΠ²Π° Π·Π°Π΄ ΡΡΡΠ°Π½Π°ΡΠ° ΡΡΠΎ ΡΠ° ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π° Π·Π° 100 ms; Π½ΠΈΠ΅ ΡΠ°ΠΌΠΈΡΠ΅ Π³ΠΎ ΠΏΠΎΡΡΠ°Π²ΡΠ²Π°ΠΌΠ΅ ΠΎΠ²Π° ΠΊΠΎΠ³Π° Π³ΠΎ Π΄Π΅Π»ΠΈΠΌΠ΅ Π²ΠΈΠ΄Π΅ΠΎ-ΡΡΡΠΈΠΌΠΎΡ ΠΏΡΠ΅Π΄ Π΄Π° Π³ΠΎ ΠΏΡΠ΅Π½Π΅ΡΠ΅ΠΌΠ΅ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠΎΡ. ΠΠΎΠΊΡΠ°Ρ ΡΠΎΠ°, ΠΊΠΎΠ³Π° Π³ΠΎ ΠΏΡΠΎΠ²Π΅ΡΠΈΠ² ΠΌΠΎΡΠΎΡ Π»Π°ΠΏΡΠΎΠΏ, ΠΏΠΎΡΡΠ΅ΠΏΠ΅Π½ΠΎ ΡΠ΅ Π°ΠΊΡΠΌΡΠ»ΠΈΡΠ°ΡΠ΅ Π·Π°ΠΎΡΡΠ°Π½ΡΠ²Π°ΡΠ΅ΡΠΎ ΠΏΠΎΠΌΠ΅ΡΡ ΡΡΡΠ°Π½ΠΈΡΠ΅ Π·Π° ΠΏΡΠ΅Π½ΠΎΡ ΠΈ ΠΏΡΠΈΠΌΠ°ΡΠ΅, ΠΎΠ²Π° Π±Π΅ΡΠ΅ ΡΠ°ΡΠ½ΠΎ Π²ΠΈΠ΄Π»ΠΈΠ²ΠΎ. ΠΠΎΡΠ½Π°Π² Π΄Π° Π±Π°ΡΠ°ΠΌ Π½Π°ΡΠΈΠ½ΠΈ Π΄Π° Π³ΠΎ Π½Π°Π΄ΠΌΠΈΠ½Π°ΠΌ ΠΎΠ²ΠΎΡ Π½Π΅Π΄ΠΎΡΡΠ°ΡΠΎΠΊ ΠΈ... Π½Π°ΠΈΠ΄ΠΎΠ²
ΠΠ·Π²ΠΎΡ: www.habr.com