䞀幎生、遠隔孊習、非同期プログラミングに぀いお

画像



私はダりンシフタヌです。たたたた、過去3幎間、劻ず私は窓の倖の田園颚景、新鮮な空気、鳥のさえずりを楜しんでいたした。家の䞭の䟿利さ、地元のプロバむダヌからの光むンタヌネット、匷力な䞭断のない電源、そしお突然珟れたコビッドは、突然、倧郜垂から移動するずいう考えをそれほど奇劙ではありたせんでした。



私が熱心にりェブ開発に携わっおいる間、バックグラりンドのどこかで、劻は私の子䟛のための孊校を遞ぶこずの問題に぀いお定期的に䞍平を蚀いたした。そしお突然子䟛は成長し、孊校の質問は盎立したした。さお、その時が来たした。䞀緒にそれを理解したしょう、土地の前の1/6の教育システムの䜕が問題になっおいたすか、そしおそれに぀いお私たちは䜕ができたすか



埓来の察面指導方法は、この蚘事の範囲倖にしおおきたす。普通の孊校には、議論の䜙地のない長所ず重倧な短所の䞡方がありたすが、ちなみに、最近、匷制的な自己隔離が远加されおいたす。ここでは、さたざたな理由で最近たすたす倚くの芪を匕き付けおいる距離ず家族の教育オプションを芋おいきたす。



明確に蚀うず、距離孊習ずは「距離孊習技術」DOTを䜿甚した通垞の孊校での授業を意味し、家族教育ずは自発的に孊校を蟞めお家族だけで孊習するこずを意味したす実際、これは叀き良き倖亀です。ただし、いずれの堎合も、少なくずも䞭間認定に合栌するためには、子䟛は利甚可胜な孊校のいずれかに所属しおいる必芁がありたす。



そしお今、人生からのいく぀かの芳察。すでに普通の孊校で勉匷しおいる子䟛たちの遠隔孊習ぞの匷制移籍で、すべおが悲しいです。孊童はこの運呜の賜物を䞀皮の䌑暇ずしお認識し、䞡芪は授業䞭に芏埋に埓うこずに慣れおおらず、その結果、党䜓的な孊業成瞟は必然的に䜎䞋したす。



䞀幎生の堎合、特に家族の堎合、䞡芪はおそらく、自然な興味ず目新しさの効果を利甚しお、子䟛を「レヌルに乗せる」機䌚がありたす。個人的には、自立するこずが䞻な仕事です。子䟛ず䞀緒に座っお宿題をしおいるず、愚かさの高さを考えたす 完党に合理的ではありたせん。もちろん、子䟛たちに人生で䜕かを成し遂げおもらいたいのなら、銖にぶら䞋がらないでください。したがっお、私の目暙は、子䟛に孊び、正しく質問し、䞀般的には自分の頭で考えるこずを教えるこずです。



芁点を぀かむ。公立孊校の遞択



たぶん、プログラムやトレヌニングスケゞュヌルを遞ぶ機䌚があるので、家族教育がもっず奜きです。そしお、あなたは物理的に孊校に通う頻床を枛らすこずができたす。ただし、9月に驚きがないように、公立孊校を遞び、監督に子䟛の配眮に぀いお話し、冬の終わりに1幎生ぞの入孊を呜じる必芁がありたす。法的な芳点からは、教育法は毎幎の蚌明を芁求しおいないようですが、私の経隓では、「締め切り」は優れた動機付けであるため、蚌明があるようにしたす。どの孊校も䞡手を広げお私たちを受け入れる可胜性は䜎いですが、最寄りの郜垂で䟡倀のある遞択肢を芋぀けるこずができるず確信しおいたす。



カリキュラムの遞択



正確に遞択したす。専門的な教育を受けずに自分でプログラムを䜜ろうずするのは合理的ではありたせん。ロシア電子孊校NESやモスクワ電子孊校 MESなどの政府の教育リ゜ヌスがありたすが 、理論的にはそれで十分ですが...どちらのオプションでも、レッスンプラン、ビデオ、テスト、チュヌトリアルが提䟛されたす。私が芋぀けられなかったのは、匷制的なカリキュラムでさえ、教科曞そのものでした。



そしおここで最も重芁なこずは欠けおいたすコミュニケヌション。無限のビデオを芋せお子䟛にテストをチェックさせるこずで子䟛を教えるこずはうたくいきたせん。぀たり、完党に独立しおレッスンを実斜するか、オンラむンスクヌルの1぀を遞択する必芁がありたす。



オンラむンスクヌルの遞択



始めたずころにほが戻っおいたす。リモコンさお、圌女を詳しく芋おみたしょう。教育プロセスをリモヌトでどのように敎理できたすかこれは倚くの質問を提起したす、私は重芁なものだけを提起したす



*ラむブコミュニケヌション。孊校は䜕を提䟛しおいたすかスカむプ、せいぜいティムス。 Skypeレッスン本圓に私が間違っおいなければ、2020幎です。䞀幎生の前で矎しいマルチカラヌのボタンが付いたいく぀かのりィンドりを開き、圌がそれらを抌さないのを埅ちたすが、退屈な叔父や叔母に半日玠盎に耳を傟けたすか私はそのような子䟛を芋たこずがありたせん。あなたも



* 宿題。より正確には、それはどのようにしおテストのために教垫に届きたすか実際、これは本圓に難しい質問であり、おそらく原則的には解決されおいたせん。既存のオプション



  1. , . --, , , , - .

  2. . , - .

  3. . , .

  4. . , , , , ? . , , .

  5. . , , . , , , . , . . , , , , . .



* 芋積り。明らかに、レッスンで䞎えられた成瞟ず宿題をチェックするずきは、䞡芪が利甚できる電子日蚘に分類されるべきです。そしお圌らはそこに着きたす。しかし、すぐにではありたせん。金のドヌムのある䞀流のリセりムの1぀を卒業した幎長の子䟛たちに尋ねたした皮肉なこずに、情報の偏りがありたす、これはなぜですかその答えは、正盎、私を驚かせたした。教垫は䞀枚の玙に成瞟を曞き留め、レッスンの埌、州のポヌタルでこの非垞に電子的な日蚘にそれらを運転するこずがわかりたした。そしお、これはテスラ゚ロンムスクが広倧なスペヌスを耕しおいる間...



さお、少し技術的な調査をしお、この状況に客芳的な理由があるかどうかを確認する時が来たしたか



架空の理想的な孊習プラットフォヌムの芁件を定矩したしょう。実際、すべおが単玔です。子䟛たちはレッスンにずどたり、教垫の蚀うこずず芋せるこずに焊点を合わせ、必芁に応じお質問に答え、必芁に応じお手を䞊げる必芁がありたす。基本的に、教垫のカメラ、プレれンテヌション、たたはホワむトボヌドからのストリヌムを含むフルスクリヌンりィンドりが必芁です。これを実珟する最も簡単な方法は、WebRTCテクノロゞヌを䜿甚するこずです。 リアルタむム通信、リアルタむム通信。これは、倚かれ少なかれ最新のブラりザで機胜し、远加の機噚を賌入する必芁がなく、さらに、高品質の接続を提䟛したす。そしお、はい、少なくずも必芁なJSメ゜ッドnavigator.mediaDevices.getUserMediaがpromiseを返すため、この暙準では非同期プログラミングが必芁です 。すべおが明確なようです、私はそれを実装し始めおいたす。



フレヌムワヌクの遞択に぀いおの叙情的な逞脱
, «» JavaScript , . jQuery. , JS :



//  
element = $(selector);
element = document.querySelector(selector);

//    
element2 = element.find(selector2);
element2 = element.querySelector(selector2);

//  
element.hide();  //   display: none
element.classList.add('hidden');

      
      





, CSS «hidden», , opacity transition, fadeIn/fadeOut CSS. , JS !



//   onClick
element.click(e => { ... });
element.onclick = (e) => { ...  }

//  
element.toggleClass(class_name);
element.classList.toggle(class_name);

//  div
div = $("<div>");
div = document.createElement("div");

//   div  element
// (  ,   )
element.append(div);
element.append(div);

      
      





. .. , JS , . , , «» JS !



WebRTCは、ポむントツヌポむントp2pテクノロゞを䜿甚しお、ブラりザ間の盎接通信甚に蚭蚈されおいたす。ただし、この接続を確立するには、ブラりザは盞互に通信する意図を通知する必芁がありたす。これにはアラヌムサヌバヌが必芁 です。



「フルメッシュ」トポロゞを䜿甚した単玔なビデオチャットの基本的な実装の䟋
'use strict';

(function () {
    const selfView = document.querySelector('#self-view'),
        remoteMaster = document.querySelector('#remote-master'),
        remoteSlaves = document.querySelector('#remote-slaves');

    let localStream,
        selfStream = null,
        socket = null,
        selfId = null,
        connections = {};

    // ***********************
    // UserMedia & DOM methods
    // ***********************

    const init = async () => {
        try {
            let stream = await navigator.mediaDevices.getUserMedia({
                audio: true, video: {
                    width: { max: 640 }, height: { max: 480 }
                }
            });
            localStream = stream;

            selfStream = new MediaStream();

            stream.getVideoTracks().forEach(track => {
                selfStream.addTrack(track, stream); // track.kind == 'video'
            });
            selfView.querySelector('video').srcObject = selfStream;

        } catch (e) {
            document.querySelector('#self-view').innerHTML =
                '<i>     </i>';
            console.error('Local stream not found: ', e);
        }
        wsInit();
    }

    const createRemoteView = (id, username) => {
        let iDiv = document.querySelector('#pc' + id);
        if (!iDiv) {
            iDiv = document.createElement('div');
            iDiv.className = 'remote-view';
            iDiv.id = 'pc' + id;

            let iVideo = document.createElement('video');
            iVideo.setAttribute('autoplay', 'true');
            iVideo.setAttribute('playsinline', 'true');

            let iLabel = document.createElement('span');

            iDiv.append(iVideo);
            iDiv.append(iLabel);

            if (!remoteMaster.querySelector('video')) {
                remoteMaster.append(iDiv);
                iLabel.textContent = '';
            } else {
                remoteSlaves.append(iDiv);
                iLabel.textContent = username;
            }
            remoteMaster.style.removeProperty('display');
        }
    }

    // *******************************
    // Signaling (Web Socket) methods
    // *******************************

    const wsInit = () => {
        socket = new WebSocket(SIGNALING_SERVER_URL);

        socket.onopen = function (e) {
            log('[socket open]  ');
        }

        socket.onmessage = function (event) {
            log('[socket message]    ', event);

            wsHandle(event.data);
        }

        socket.onclose = function (event) {
            if (event.wasClean) {
                log('[close]   , ' +
                    `=${event.code} =${event.reason}`);
            } else {
                log('[socket close]  ', event);
            }
            clearInterval(socket.timer);
        }

        socket.onerror = function (error) {
            logError('[socket error]', error);
        }

        socket.timer = setInterval(() => {
            socket.send('heartbeat');
        }, 10000);
    }

    const wsHandle = async (data) => {
        if (!data) {
            return;
        }
        try {
            data = JSON.parse(data);
        } catch (e) {
            return;
        }

        switch (data.type) {
            case 'handshake':
                selfId = data.uid;
                if (!Object.keys(data.users).length) {
                    createRemoteView(selfId, '');
                    remoteMaster.querySelector('video').srcObject =
                        selfStream;
                    selfView.remove();
                    break;
                } else {
                    selfView.style.removeProperty('display');
                }
                for (let id in data.users) {
                    await pcCreate(id, data.users[id]);
                }
                break;
            case 'offer':
                await wsHandleOffer(data);
                break;
            case 'answer':
                await wsHandleAnswer(data)
                break;
            case 'candidate':
                await wsHandleICECandidate(data);
                break;
            default:
                break;
        }
    }

    const wsHandleOffer = async (data) => {
        let pc = null;

        if (!connections[data.src]) {
            await pcCreate(data.src, data.username);
        }

        pc = connections[data.src].pc;

        // We need to set the remote description to the received SDP offer
        // so that our local WebRTC layer knows how to talk to the caller.
        let desc = new RTCSessionDescription(data.sdp);

        pc.setRemoteDescription(desc).catch(error => {
            logError('handleOffer', error);
        });

        await pc.setLocalDescription(await pc.createAnswer());

        wsSend({
            type: 'answer',
            target: data.src,
            sdp: pc.localDescription
        });

        connections[data.src].pc = pc; // ???
    }

    const wsHandleAnswer = async (data) => {
        log('*** Call recipient has accepted our call, answer:', data);

        let pc = connections[data.src].pc;

        // Configure the remote description,
        // which is the SDP payload in our 'answer' message.

        let desc = new RTCSessionDescription(data.sdp);
        await pc.setRemoteDescription(desc).catch((error) => {
            logError('handleAnswer', error);
        });
    }

    const wsHandleICECandidate = async (data) => {
        let pc = connections[data.src].pc;

        let candidate = new RTCIceCandidate(data.candidate);

        log('*** Adding received ICE candidate', candidate);

        pc.addIceCandidate(candidate).catch(error => {
            logError('handleICECandidate', error);
        });
    }

    const wsSend = (data) => {
        if (socket.readyState !== WebSocket.OPEN) {
            return;
        }
        socket.send(JSON.stringify(data));
    }

    // ***********************
    // Peer Connection methods
    // ***********************

    const pcCreate = async (id, username) => {
        if (connections[id]) {
            return;
        }
        try {
            let pc = new RTCPeerConnection(PC_CONFIG);

            pc.onicecandidate = (event) =>
                pcOnIceCandidate(event, id);
            pc.oniceconnectionstatechange = (event) =>
                pcOnIceConnectionStateChange(event, id);
            pc.onsignalingstatechange =  (event) =>
                pcOnSignalingStateChangeEvent(event, id);
            pc.onnegotiationneeded = (event) =>
                pcOnNegotiationNeeded(event, id);
            pc.ontrack = (event) =>
                pcOnTrack(event, id);

            connections[id] = {
                pc: pc,
                username: username
            }

            if (localStream) {
                try {
                    localStream.getTracks().forEach(
                        (track) => connections[id].pc.addTransceiver(track, {
                            streams: [localStream]
                        })
                    );
                } catch (err) {
                    logError(err);
                }
            } else {
                // Start negotiation to listen remote stream only
                pcOnNegotiationNeeded(null, id);
            }
            createRemoteView(id, username);
        } catch (error) {
            logError('Peer: Connection failed', error);
        }
    }

    const pcOnTrack = (event, id) => {
        let iVideo = document.querySelector('#pc' + id + ' video');
        iVideo.srcObject = event.streams[0];
    }

    const pcOnIceCandidate = (event, id) => {
        let pc = connections[id].pc;

        if (event.candidate && pc.remoteDescription) {
            log('*** Outgoing ICE candidate: ' + event.candidate);
            wsSend({
                type: 'candidate',
                target: id,
                candidate: event.candidate
            });
        }
    }

    const pcOnNegotiationNeeded = async (event, id) => {
        let pc = connections[id].pc;
        try {
            const offer = await pc.createOffer();

            // If the connection hasn't yet achieved the "stable" state,
            // return to the caller. Another negotiationneeded event
            // will be fired when the state stabilizes.
            if (pc.signalingState != 'stable') {
                return;
            }

            // Establish the offer as the local peer's current
            // description.
            await pc.setLocalDescription(offer);

            // Send the offer to the remote peer.
            wsSend({
                type: 'offer',
                target: id,
                sdp: pc.localDescription
            });
        } catch(err) {
            logError('*** The following error occurred while handling' +
                ' the negotiationneeded event:', err);
        };
    }

    const pcOnIceConnectionStateChange = (event, id) => {
        let pc = connections[id].pc;
        switch (pc.iceConnectionState) {
            case 'closed':
            case 'failed':
            case 'disconnected':
                pcClose(id);
                break;
        }
    }

    const pcOnSignalingStateChangeEvent = (event, id) => {
        let pc = connections[id].pc;

        log('*** WebRTC signaling state changed to: ' + pc.signalingState);

        switch (pc.signalingState) {
            case 'closed':
                pcClose(id);
                break;
        }
    }

    const pcClose = (id) => {
        let remoteView = document.querySelector('#pc' + id);

        if (connections[id]) {
            let pc = connections[id].pc;
            pc.close();
            delete connections[id];
        }
        if (remoteView) {
            remoteView.remove();
        }
    }

    // *******
    // Helpers
    // *******

    const log = (msg, data) => {
        if (!data) {
            data = ''
        }
        console.log(msg, data);
    }

    const logError = (msg, data) => {
        if (!data) {
            data = ''
        }
        console.error(msg, data);
    }

    init();
})();

      
      







シグナリングサヌバヌはPythonaiohttpフレヌムワヌク䞊で䜜成され、WebRTC芁求を簡単にプロキシする単玔な「ビュヌ」です。この䟋のサヌバヌぞの接続は、Web゜ケットを介しお行われ たす。さらに、単玔なテキストチャットデヌタがシグナリングチャネルを介しお送信されたす。



シグナリングサヌバヌの実装䟋
import json
from aiohttp.web import WebSocketResponse, Response
from aiohttp import WSMsgType
from uuid import uuid1
from lib.views import BaseView


class WebSocket(BaseView):
    """ Process WS connections """

    async def get(self):
        username = self.request['current_user'].firstname or ''

        room_id = self.request.match_info.get('room_id')

        if room_id != 'test_room' and
            self.request['current_user'].is_anonymous:
            self.raise_error('forbidden')  # @TODO: send 4000

        if (self.request.headers.get('connection', '').lower() != 'upgrade' or
            self.request.headers.get('upgrade', '').lower() != 'websocket'):
            return Response(text=self.request.path)  # ???

        self.ws = WebSocketResponse()
        await self.ws.prepare(self.request)

        self.uid = str(uuid1())

        if room_id not in self.request.app['web_sockets']:
            self.request.app['web_sockets'][room_id] = {}

        self.room = self.request.app['web_sockets'][room_id]

        users = {}
        for id, data in self.room.items():
            users[id] = data['name']

        ip = self.request.headers.get(
            'X-FORWARDED-FOR',
            self.request.headers.get('X-REAL-IP',
            self.request.remote))

        msg = {
            'type': 'handshake',
            'uid': str(self.uid),
            'users': users, 'ip': ip}
        await self.ws.send_str(json.dumps(msg, ensure_ascii=False))

        self.room[self.uid] = {'name': username, 'ws': self.ws}

        try:
            async for msg in self.ws:
                if msg.type == WSMsgType.TEXT:
                    if msg.data == 'heartbeat':
                        print('---heartbeat---')
                        continue

                    try:
                        msg_data = json.loads(msg.data)

                        if 'target' not in msg_data or
                            msg_data['target'] not in self.room:
                            continue

                        msg_data['src'] = self.uid

                        if 'type' in msg_data and 'target' in msg_data:
                            if msg_data['type'] == 'offer':
                                msg_data['username'] = username
                        else:
                            print('INVALID DATA:', msg_data)
                    except Exception as e:
                        print('INVALID JSON', e, msg)

                    try:
                        await self.room[msg_data['target']]['ws'].send_json(
                            msg_data);
                    except Exception as e:
                        if 'target' in msg_data:
                            self.room.pop(msg_data['target'])

        finally:
            self.room.pop(self.uid)

        return self.ws

      
      







WebRTCテクノロゞを䜿甚するず、ビデオ通信に加えお、ディスプレむたたは別のアプリケヌションのコンテンツをキャプチャする蚱可をブラりザに䞎える こずができたす。これは、オンラむンレッスン、りェビナヌ、たたはプレれンテヌションを行うずきに䞍可欠です。よし、䜿っおみよう。



私はビデオ通信の珟代的な可胜性に倢䞭になり、クラスで最も重芁な䞻題であるむンタラクティブホワむトボヌドに぀いおほずんど忘れおいたした。ただし、基本的な実装は非垞に簡単なので、この蚘事をそれで過負荷にするこずはありたせん。キャンバスを远加し、 onmousemoveタブレットの堎合はontouchmoveのマりス移動むベントをリッスンし、結果の座暙を同じシグナリングサヌバヌを介しお接続されおいるすべおのポむントに送信したす。



むンタラクティブホワむトボヌドのテスト



ここでは、タブレット、デゞタむザヌ、生きおいる子䟛が必芁です。同時に、手曞き入力のデゞタル化の可胜性を確認したす。



たず、Android4.4の叀いGalaxyTabタブレット、自家補のスタむラス、そしおキャンバスの背景ずしお最初に出䌚ったコピヌブックを取り䞊げたした。远加のプログラムはむンストヌルしたせんでした。結果は私を萜胆させたした私のタブレットは曞くのに絶察に適しおいたせん぀たり、指をそれに沿っお動かすこずは問題ありたせんが、䞋の写真のような巚倧なものであっおも、スタむラスを文字の茪郭に入れるこずはすでに問題です。さらに、ガゞェットは描画の過皋で鈍くなり始め、その結果、線が途切れたす。さらに、私は子䟛に手銖を画面に乗せないようにするこずができなかったため、手元に远加の塗り぀ぶしが残り、タブレット自䜓がさらに遅くなり始めたした。結論ホワむトボヌドに曞き蟌むための通垞のタブレットは適しおいたせん。その機胜の最倧は、画面䞊でかなり倧きな数字を指で動かすこずです。しかし、これを孊童に提䟛するには遅すぎたす。



さお、これは玔粋に理論的な研究ですよね次に、デゞタむザヌ別名グラフィックタブレットのWacom Bamboo A8圢匏を䜿甚しお、子䟛を監芖したす。



私の6歳の被隓者は、圌の人生で初めおグラフィックペン付きのラップトップを受け取ったこずに泚意しおください。ペンを䜿甚するための基本的なスキルを習埗するのに10分かかりたした。すでに、2番目のレッスンでは、子䟛は非垞に自信を持っおタブレットを䜿甚し、ボヌドから独立しお消去し、顔、花、犬を描き、むプシロンで利甚可胜なボタンを突くようになり、同時に次のような質問をしたした。 「なぜ圌らは孊校で手を䞊げるのですか」しかし、その結果は垞に倚くのこずが望たれおいたした。事実、デザむナヌやアヌティストは画像の断片を最倧化しお芁玠を描画し、線を正確にしたす。ここでは、ボヌド党䜓が11のスケヌルで衚瀺されたす。ここず倧人は列に萜ちたせん。これが私たちが埗たものです



画像



最終評決手曞きは問題倖ではありたせん。そしお、私たちが子䟛たちに「手を差し䌞べる」こずを望むなら、私たちは自分たちでこれを達成する必芁がありたす、玙の䞊で、孊校はこれを助けたせん。



子䟛は私のすべおの実隓を熱心に受け止め、さらにそれ以来、私を尻尟で远いかけ、「レシピをオンにする」ように頌んできたず蚀わなければなりたせん。すでに十分に、習埗したスキルは、たったく異なる目的のためにのみ、圌に圹立ちたす。



ずにかく、実隓の結果、私は実際にMVPを手に入れたした-最小限の実行可胜な補品、 ほがビデオ/オヌディオ䌚議、共有画面、むンタラクティブホワむトボヌド、シンプルなテキストチャット、挙手ボタンなど、オンラむンレッスンに適しおいたす。これは、子䟛が突然マむクを持っおいない堎合に備えおいたす。はい、これは特にレッスンを孊んでいない子䟛たちの間で起こりたす。



しかし、この蜂蜜の暜には、残念ながら、タヌルスプヌンがいく぀かありたす。



WebRTCのテスト



スプヌン番号1。私たちのビデオ通信はクラむアント間の盎接接続を䜿甚するため、最初のステップはそのような゜リュヌションのスケヌラビリティをテストするこずです。テストでは、デュアルコアi5-3230Mを搭茉した叀いラップトップを䜿甚しお、Webカメラが無効になっおいるクラむアントを接続し始めたした。぀たり、1察倚モヌドを゚ミュレヌトしたした。



画像



ご芧のずおり、実隓甚ラップトップは5人のクラむアントに倚かれ少なかれ快適にブロヌドキャストできたす CPU負荷が60以内の堎合。これは、発信ビデオストリヌムの解像床が720p640x480pxに、フレヌムレヌトが15fpsに䜎䞋した堎合に提䟛されたす。原則ずしお、それほど悪くはありたせんが、数十人の孊生のクラスを接続する堎合、カスケヌドを優先しお「フルメッシュ」を攟棄する必芁がありたす。぀たり、最初の5぀のクラむアントのそれぞれがストリヌムを次の5぀にプロキシしたす。



スプヌン番号2。クラむアント間に盎接察話型接続ICEを䜜成するには、クラむアントはファむアりォヌルずNATをバむパスする必芁がありたす。これを行うために、WebRTCはSTUNサヌバヌを䜿甚しお、 クラむアントに倖郚接続パラメヌタヌを通知したす。ほずんどの堎合、これで十分であるず考えられおいたす。しかし、私はほずんどすぐに「幞運」でした。



ご芧のずおり、デバッガヌはICE接続が䞍可胜であるず文句を蚀い、TURNサヌバヌを接続する必芁がありたす。぀たり、リレヌです。そしお、これはすでに高䟡です。ここでは、シンプルなシグナリングサヌバヌが䞍可欠です。結論-すべおのストリヌムをメディアサヌバヌに枡す必芁がありたす。



メディアサヌバヌ



テストには、aiortcを䜿甚し たした。興味深い開発であり、WebRTCを介しおブラりザをサヌバヌに盎接接続できたす。個別のシグナリングは必芁ありたせん。接続自䜓のデヌタチャネルを䜿甚できたす。これは機胜し、すべおのテストポむントが問題なく接続されたした。しかし、パフォヌマンスの問題。同じ720pず15fpsの制限を持぀ビデオ/オヌディオストリヌムの単玔な゚コヌは、テストVDSの仮想CPUの50を消費したした。さらに、負荷が100に増加するず、ビデオストリヌムをクラむアントにアンロヌドする時間がなくなり、メモリが詰たり始め、最終的にサヌバヌが停止したす。明らかに、私たちがI / O凊理に䜿甚するのが倧奜きなPythonは、CPUにあたり䟝存しおいたせん。Janusなど、より専門的な゜リュヌションを探す必芁がありたす たたは Jitsy。



いずれにせよ、私の芋積もりによれば、完党な゜リュヌションには、郚屋クラスごずに1コアの割合で専甚サヌバヌが必芁になりたす。これにはすでに費甚がかかり、単玔なテストを超えおいるため、この時点で、研究の最初のフェヌズが完了したず芋なしたす。



結論



1.控えめに蚀っお、ロシア連邊の公匏ポヌタルで、以前の朜圚的な敵番号1ここではMicrosoft Teamsに぀いおのプログラムに登録するためのダりンロヌド手順ずリンクを芋るのは奇劙 です。そしお、これは制裁ず茞入代替の時代です。

いいえ、個人的に私は人々の友情、そしお䞀般的にはあらゆる皮類の寛容のためですが、私の髪が逆立っおいるのは本圓にそのような「統合」からの私だけですか私たちの開発はありたせんか



2. MES / NESず孊校の統合。実際、MES開発者は玠晎らしく、Yandex.tutorずの統合も行っおいたす。レッスン䞭のリアルタむムのグレヌディングはどうですかAPIはい぀ありたすかそれずも私は䜕かに気づいおいたせんか



3.距離や家族の教育圢態を遞択するずきは、自分自身に正盎である必芁がありたす。子䟛の教育の責任を孊校に移すこずはできたせん。レッスンの実斜家族教育の堎合、芏埋の維持、自己組織化いずれの堎合ものすべおの䜜業は、完党に芪に委ねられおいたす。これを認識し、授業の時間を芋぀ける必芁がありたす。ただし、倧家族では、これは問題にはならないはずです。



4.広告ず芋なされないように、ここでは遞択したオンラむン孊校ぞのリンクを含めたせん。䞭䟡栌垯の私立孊校を遞んだずだけ蚀っおおきたす。いずれにせよ、最終的な結果は子䟛によっお異なり、9月たでに受け取りたす。



それずも、ここで始たった開発を論理的な結論に導き、あなた自身の孊校を組織するこずは理にかなっおいたすかどう思いたすか教育の分野で専門的な知識ず経隓を持぀志を同じくする人々はいたすか



䟿利なリンク

ロシア電子孊校

モスクワ電子孊校 開発者向け

MESラむブラリ




All Articles