рдПрдХ рдмрд╛рд░ рдЬрдм рдореИрдВрдиреЗ рдПрдХ рдлрд┐рд▓реНрдо рджреЗрдЦреА , рдФрд░ рд╡рд╣ рдХреНрд╖рдг рдореЗрд░реА рдпрд╛рдж рдореЗрдВ рдЕрдВрдХрд┐рдд рд╣реЛ рдЧрдпрд╛ , рдЬрдм рдлрд┐рд▓реНрдо рдХреЗ рдореБрдЦреНрдп рдкрд╛рддреНрд░реЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдиреЗ рдЕрдкрдиреЗ рдкреИрд░ рдФрд░ рд╣рд╛рде рдХреЛ рдорд╛рдзреБрд░реНрдп рдХреА рд▓рдп рдореЗрдВ рдмрдврд╝рд╛ рджрд┐рдпрд╛ред рддреЛ: рди рдХреЗрд╡рд▓ рдирд╛рдпрдХ рдЙрд▓реНрд▓реЗрдЦрдиреАрдп рд╣реИ, рдмрд▓реНрдХрд┐ рдЙрд╕рдХреЗ рдкреАрдЫреЗ рдореЙрдирд┐рдЯрд░ рдХрд╛ рд╕реЗрдЯ рд╣реИред
рд╣рд╛рд▓ рд╣реА рдореЗрдВ, рдХрдИ рдЕрдиреНрдп рд▓реЛрдЧреЛрдВ рдХреА рддрд░рд╣, рдЪрд╛рд░ рджреАрд╡рд╛рд░реЛрдВ рдореЗрдВ рдмрд┐рддрд╛рдП рд╕рдордп рдХреА рдорд╛рддреНрд░рд╛ рдмрд╣реБрдд рдмрдврд╝ рдЧрдИ рд╣реИ, рдФрд░ рдпрд╣ рд╡рд┐рдЪрд╛рд░ рдЖрдпрд╛: "рдФрд░ рдЗрд╕ рддрд░рд╣ рдХреЗ рджреГрд╢реНрдп рдХреЛ рдорд╣рд╕реВрд╕ рдХрд░рдиреЗ рдХрд╛ рд╕рдмрд╕реЗ рдЕрдЬреАрдм рддрд░реАрдХрд╛ рдХреНрдпрд╛ рд╣реИ?"
рдЖрдЧреЗ рджреЗрдЦрддреЗ рд╣реБрдП, рдореИрдВ рдХрд╣реВрдВрдЧрд╛ рдХрд┐ рдкрд╕рдВрдж рд╡реЗрдм рдмреНрд░рд╛рдЙрдЬрд╝рд░, WebRTC рдФрд░ WebAudio API рдХреЗ рдЙрдкрдпреЛрдЧ рдкрд░ рдЧрд┐рд░ рдЧрдИред
рддреЛ рдореИрдВрдиреЗ рд╕реЛрдЪрд╛, рдФрд░ рдлрд┐рд░ рддреБрд░рдВрдд, рд╕рд░рд▓ рд╕реЗ (рд░реЗрдбрд┐рдпреЛ рдЙрдард╛рдПрдВ рдФрд░ рдРрд╕рд╛ рдЦрд┐рд▓рд╛рдбрд╝реА рдЦреЛрдЬреЗрдВ, рдЬрд┐рд╕рдореЗрдВ рдРрд╕рд╛ рд╡рд┐рдЬрд╝реБрдЕрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рд╣реЛ) рд╡рд┐рдХрд▓реНрдк рдХреЛ рд▓рдВрдмреЗ рд▓реЛрдЧреЛрдВ рддрдХ рд▓реЗ рдЬрд╛рдПрдВ (рдХреНрд▓рд╛рдЗрдВрдЯ-рд╕рд░реНрд╡рд░ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрдирд╛рдПрдВ рдЬреЛ рд╕реЙрдХреЗрдЯ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд░рдВрдЧ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рднреЗрдЬреЗрдЧрд╛)ред рдФрд░ рдлрд┐рд░ рдореИрдВрдиреЗ рдЦреБрдж рдХреЛ рдпрд╣ рд╕реЛрдЪрддреЗ рд╣реБрдП рдкрдХрдбрд╝рд╛: "рдЕрдм рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рднреА рдШрдЯрдХ рд╣реИрдВ", рдЗрд╕рд▓рд┐рдП рдореИрдВ рдЗрд╕реЗ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реВрдБрдЧрд╛ред
WebRTC, рдбреЗрдЯрд╛ рдХреЛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕реЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░ (рдкреАрдпрд░-рдЯреВ-рдкреАрдпрд░) рдореЗрдВ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХрд╛ рдПрдХ рддрд░реАрдХрд╛ рд╣реИ, рдЬрд┐рд╕рдХрд╛ рдЕрд░реНрде рд╣реИ рдХрд┐ рдореБрдЭреЗ рдкрд╣рд▓реЗ рд╕рд░реНрд╡рд░ рдмрдирд╛рдиреЗ рдХреА рдЬрд╝рд░реВрд░рдд рдирд╣реАрдВ рд╣реИред рд▓реЗрдХрд┐рди рд╡рд╣ рдереЛрдбрд╝рд╛ рдЧрд▓рдд рдерд╛ред RTCPeerConnection рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рджреЛ рд╕рд░реНрд╡рд░ рдЪрд╛рд╣рд┐рдП: рд╕рд┐рдЧреНрдирд▓ рдФрд░ ICEред рджреВрд╕рд░реЗ рдХреЗ рд▓рд┐рдП, рдЖрдк рдПрдХ рддреИрдпрд╛рд░ рдХрд┐рдП рдЧрдП рд╕рдорд╛рдзрд╛рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (STUN рдпрд╛ TURN рд╕рд░реНрд╡рд░ рдХрдИ рд▓рд┐рдирдХреНрд╕ рд╡рд┐рддрд░рдг рдХреЗ рднрдВрдбрд╛рд░ рдореЗрдВ рд╣реИрдВ)ред рдкрд╣рд▓реЗ рдХреЗ рд╕рд╛рде рдХреБрдЫ рдХрд░рдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИред
рдкреНрд░рд▓реЗрдЦрди рдХрд╛ рдХрд╣рдирд╛ рд╣реИ рдХрд┐ рдПрдХ рдордирдорд╛рдирд╛ рджреЛ-рддрд░рдлрд╝рд╛ рдЗрдВрдЯрд░реИрдХреНрд╢рди рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдПрдХ рд╕рдВрдХреЗрдд рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рд░реНрдп рдХрд░ рд╕рдХрддрд╛ рд╣реИ, рдФрд░ рдлрд┐рд░ рддреБрд░рдВрдд рд╡реЗрдмрд╕реНрдХреЗрдЯ, рд▓реЙрдиреНрдЧ рдкреВрд▓рд┐рдВрдЧ рдпрд╛ рдХреБрдЫ рдЕрд▓рдЧ рдХрд░ рд╕рдХрддрд╛ рд╣реИред рдореБрдЭреЗ рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рд╕рдмрд╕реЗ рдЖрд╕рд╛рди рд╡рд┐рдХрд▓реНрдк рдХреБрдЫ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рдкреНрд░рд▓реЗрдЦрди рд╕реЗ рд╣реИрд▓реЛ рджреБрдирд┐рдпрд╛ рд▓реЗрдирд╛ рд╣реИред рдФрд░ рдпрд╣рд╛рдБ рдПрдХ рд╕рд░рд▓ рд╕рд┐рдЧреНрдирд▓ рд╕рд░реНрд╡рд░ рд╣реИ:
import os
from aiohttp import web, WSMsgType
routes = web.RouteTableDef()
routes.static("/def", os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static') )
@routes.post('/broadcast')
async def word(request):
for conn in list(ws_client_connections):
data = await request.text()
await conn.send_str(data)
return web.Response(text="Hello, world")
ws_client_connections = set()
async def websocket_handler(request):
ws = web.WebSocketResponse(autoping=True)
await ws.prepare(request)
ws_client_connections.add(ws)
async for msg in ws:
if msg.type == WSMsgType.TEXT:
if msg.data == 'close':
await ws.close()
else:
continue
elif msg.type == WSMsgType.ERROR:
print('conn lost')
return ws
if __name__ == '__main__':
app = web.Application()
app.add_routes(routes)
app.router.add_get('/ws', websocket_handler)
web.run_app(app)
рдореИрдВрдиреЗ рдХреНрд▓рд╛рдЗрдВрдЯ рд╡реЗрдмрд╕реЙрдХреЗрдЯ рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЛ рднреА рд▓рд╛рдЧреВ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдерд╛, рд▓реЗрдХрд┐рди рдмрд╕ рдПрдХ POST рд╕рдорд╛рдкрди рдмрд┐рдВрджреБ рдмрдирд╛рдпрд╛, рдЬреЛ рд╕рднреА рдХреЛ рд╕рдВрджреЗрд╢ рднреЗрдЬрддрд╛ рд╣реИред рдкреНрд░рд▓реЗрдЦрди рд╕реЗ рдХреЙрдкреА рдХрд░рдиреЗ рдХрд╛ рддрд░реАрдХрд╛ рдореЗрд░реА рдкрд╕рдВрдж рдХрд╛ рд╣реИред
рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЗ рдмреАрдЪ WebRTC рдХрдиреЗрдХреНрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдПрдХ рдЕрдзреВрд░рд╛ рд╣рд╛рдп-рд╣рд╛рдп рд╣реЛрддрд╛ рд╣реИ, рдФрд░ рдЖрдк рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ - рдФрд░ рдореИрдВ рдХрд░ рд╕рдХрддрд╛ рд╣реВрдБред рдЖрд░реЗрдЦ рдмрд╣реБрдд рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рджрд┐рдЦрд╛рдИ рджреЗрддрд╛ рд╣реИ:

( рдкреГрд╖реНрда рд╕реЗ рд▓рд┐рдпрд╛ рдЧрдпрд╛ рдЪрд╛рд░реНрдЯ )
рдкрд╣рд▓реЗ рдЖрдкрдХреЛ рд╕реНрд╡рдпрдВ рдХрдиреЗрдХреНрд╢рди рдмрдирд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:
function openConnection() {
const servers = { iceServers: [
{
urls: [`turn:${window.location.hostname}`],
username: 'rtc',
credential: 'demo'
},
{
urls: [`stun:${window.location.hostname}`]
}
]};
let localConnection = new RTCPeerConnection(servers);
console.log('Created local peer connection object localConnection');
dataChannelSend.placeholder = '';
localConnection.ondatachannel = receiveChannelCallback;
localConnection.ontrack = e => {
consumeRemoteStream(localConnection, e);
}
let sendChannel = localConnection.createDataChannel('sendDataChannel');
console.log('Created send data channel');
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
return localConnection;
}
, , . createDataChannel() , , addTrack() .
function createConnection() {
if(!iAmHost){
alert('became host')
return 0;
}
for (let listener of streamListeners){
streamListenersConnections[listener] = openConnection()
streamListenersConnections[listener].onicecandidate = e => {
onIceCandidate(streamListenersConnections[listener], e, listener);
};
audioStreamDestination.stream.getTracks().forEach(track => {
streamListenersConnections[listener].addTrack(track.clone())
})
streamListenersConnections[listener].createOffer().then((offer) =>
gotDescription1(offer, listener),
onCreateSessionDescriptionError
).then( () => {
startButton.disabled = true;
closeButton.disabled = false;
});
}
}
WebAudio API. , , , .
function createAudioBufferFromFile(){
let fileToProcess = new FileReader();
fileToProcess.readAsArrayBuffer(selectedFile.files[0]);
fileToProcess.onload = () => {
let audioCont = new AudioContext();
audioCont.decodeAudioData(fileToProcess.result).then(res => {
let source = audioCont.createBufferSource()
audioSource = source;
source.buffer = res;
let dest = audioCont.createMediaStreamDestination()
source.connect(dest)
audioStreamDestination =dest;
source.loop = false;
iAmHost = true;
});
}
}
, , mp3 . AudioContext, MediaStream. , , addTrack() WebRTC .
, createOffer() . , , :
function acceptDescription2(desc, broadcaster) {
return localConnection.setRemoteDescription(desc)
.then( () => {
return localConnection.createAnswer();
})
.then(answ => {
return localConnection.setLocalDescription(answ);
}).then(() => {
postData(JSON.stringify({type: "accept", user:username.value, to:broadcaster, descr:localConnection.currentLocalDescription}));
})
}
IceCandiates:
function finalizeCandidate(val, listener) {
console.log('accepting connection')
const a = new RTCSessionDescription(val);
streamListenersConnections[listener].setRemoteDescription(a).then(() => {
dataChannelSend.disabled = false;
dataChannelSend.focus();
sendButton.disabled = false;
processIceCandiates(listener)
});
}
:
let conn = localConnection? localConnection: streamListenersConnections[data.user]
conn.addIceCandidate(data.candidate).then( onAddIceCandidateSuccess, onAddIceCandidateError);
processIceCandiates(data.user)
addIceCandidate() .
, .
/( ).
function consumeRemoteStream(localConnection, event) {
console.log('consume remote stream')
const styles = {
position: 'absolute',
height: '100%',
width: '100%',
top: '0px',
left: '0px',
'z-index': 1000
};
Object.keys(styles).map(i => {
canvas.style[i] = styles[i];
})
let audioCont = new AudioContext();
audioContext = audioCont;
let stream = new MediaStream();
stream.addTrack(event.track)
let source = audioCont.createMediaStreamSource(stream)
let analyser = audioCont.createAnalyser();
analyser.smoothingTimeConstant = 0;
analyser.fftSize = 2048;
source.connect(analyser);
audioAnalizer = analyser;
audioSource = source;
analyser.connect(audioCont.destination)
render()
}
, . AudioContext , AudioNode.
, createAnalyser() .
:
function render(){
var freq = new Uint8Array(audioAnalizer.frequencyBinCount);
audioAnalizer.getByteFrequencyData(freq);
let band = freq.slice(
Math.floor(freqFrom.value/audioContext.sampleRate*2*freq.length),
Math.floor(freqTo.value/audioContext.sampleRate*2*freq.length));
let avg = band.reduce((a,b) => a+b,0)/band.length;
context.fillStyle = `rgb(
${Math.floor(255 - avg)},
0,
${Math.floor(avg)})`;
context.fillRect(0,0,200,200);
requestAnimationFrame(render.bind(gl));
}
рдкреВрд░реЗ рд╕реНрдкреЗрдХреНрдЯреНрд░рдо рд╕реЗ, рдПрдХ рдмреИрдВрдб рдХреЛ рд╣рд╛рдЗрд▓рд╛рдЗрдЯ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬрд┐рд╕реЗ рдПрдХрд▓ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рджреНрд╡рд╛рд░рд╛ рджрд░реНрд╢рд╛рдпрд╛ рдЬрд╛рдПрдЧрд╛, рдФрд░ рдФрд╕рддрди, рдЖрдпрд╛рдо рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЕрдЧрд▓рд╛, рдмрд╕ рд▓рд╛рд▓ рдФрд░ рдиреАрд▓реЗ рд░рдВрдЧ рдХреЗ рдмреАрдЪ рдПрдХ рд╣реА рдЖрдпрд╛рдо рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рд░рдВрдЧ рдмрдирд╛рддреЗ рд╣реИрдВред рдХреИрдирд╡рд╛рд╕ рдбреНрд░рд╛ рдХрд░реЗрдВред
рдРрд╕рд╛ рдХреБрдЫ рдХрд▓реНрдкрдирд╛ рдХреЗ рдлрд▓ рдХреЗ рдЙрдкрдпреЛрдЧ рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рджрд┐рдЦрддрд╛ рд╣реИ: