حول طلاب الصف الأول والتعلم عن بعد والبرمجة غير المتزامنة

صورة



أنا منحدر. لقد حدث أنه على مدار السنوات الثلاث الماضية ، استمتعت أنا وزوجتي بالمناظر الطبيعية الريفية خارج النافذة والهواء النقي وأغنية العصافير. وسائل الراحة في المنزل ، والإنترنت البصري من مزود محلي ، وإمدادات الطاقة القوية غير المنقطعة ، والفيروس الذي ظهر فجأة ، جعلت فكرة الانتقال من مدينة ليست غريبة جدًا.



بينما كنت منخرطًا بحماس في تطوير الويب ، في مكان ما في الخلفية ، اشتكت زوجتي بشكل دوري من مشاكل اختيار مدرسة لطفلي. ثم (فجأة) كبر الطفل ووقف سؤال المدرسة في وضع مستقيم. حسنًا ، لقد حان الوقت. لنكتشفها معًا ، ما الخطأ في نظام التعليم في 1/6 السابقة من الأرض ، وماذا يمكننا أن نفعل حيال ذلك؟



سأترك طرق التدريس التقليدية وجهاً لوجه خارج نطاق هذا المقال. سأقول فقط أن المدارس العادية لها مزايا لا جدال فيها وعيوب خطيرة ، والتي ، بالمناسبة ، تمت إضافة العزلة الذاتية القسرية مؤخرًا. هنا نلقي نظرة على خيارات التعليم عن بعد والأسرة التي جذبت مؤخرًا عددًا متزايدًا من الآباء لأسباب متنوعة.



لكي نكون واضحين: التعلم عن بعد يعني ضمناً فصولاً في مدرسة عادية تستخدم "تقنيات التعلم عن بعد" (DOT) ، والتعليم الأسري يعني ترك المدرسة الطوعي والتعلم من قبل الأسرة فقط (في الواقع ، هذا تدريب قديم جيد). ومع ذلك ، في أي حال ، يجب تعيين الطفل في أي من المدارس المتاحة ، على الأقل لاجتياز الشهادات المتوسطة.



والآن بعض الملاحظات من الحياة. مع النقل القسري إلى التعلم عن بعد للأطفال الذين درسوا بالفعل في مدرسة عادية ، كل شيء محزن. ينظر تلاميذ المدارس إلى هدية القدر هذه كنوع من الإجازة ، ولا يعتاد الآباء على اتباع الانضباط أثناء الفصول الدراسية ، ونتيجة لذلك ، ينخفض ​​الأداء الأكاديمي بشكل عام.



مع طلاب الصف الأول ، خاصة في حالة شكل الأسرة ، ربما يكون لدى الوالدين فرصة لوضع الطفل "على القضبان" باستخدام الفائدة الطبيعية وتأثير الجدة. بالنسبة لي شخصيًا ، فإن تحقيق الاستقلال هو المهمة الرئيسية. أجلس وأؤدي واجباتي المدرسية مع طفل ، أنا أعتبر ذروة الغباءليس منطقيا تماما. بالطبع ، إذا كنت تريد أن يحقق أطفالك شيئًا ما في الحياة ولا يتسكعون حول رقبتك. لذلك ، فإن هدفي هو تعليم الطفل أن يتعلم ، وطرح الأسئلة بشكل صحيح ، وبشكل عام ، التفكير برأسه.



أوضح ماذا تقصد. اختيار مدرسة عامة



ربما أحب التربية الأسرية أكثر بسبب فرصة اختيار البرنامج وجدول التدريب. ويمكنك حضور المدرسة جسديًا بمعدل أقل. لكن عليك اختيار مدرسة عامة ، والتحدث مع المدير عن وضع الطفل والحصول على أمر بالتسجيل في الصف الأول في نهاية الشتاء حتى لا تكون هناك مفاجآت في سبتمبر. على الرغم من أنه ، من وجهة نظر قانونية ، لا يبدو أن قانون التعليم يتطلب شهادات سنوية ، فإن "المواعيد النهائية" ، في تجربتي ، هي محفزات ممتازة ، لذا دعنا نوفر شهادات. من غير المحتمل أن تقبلنا أي مدرسة بأذرع مفتوحة ، لكنني متأكد من أننا سنكون قادرين على إيجاد خيار لائق في أقرب مدينة.



اختيار المنهج



نختار بالضبط. إن محاولة تأليف برنامج بنفسك دون الحصول على تعليم متخصص أمر غير معقول. على الرغم من وجود موارد تعليمية حكومية مثل المدرسة الإلكترونية الروسية ( NES ) ومدرسة موسكو الإلكترونية ( MES ) ، والتي ستكون كافية من الناحية النظرية ، ولكن ... كلا الخيارين يوفران خطط الدروس ومقاطع الفيديو والاختبارات والبرامج التعليمية. ما لم أجده هو الكتب المدرسية نفسها ، حتى للمناهج الإجبارية.



وهنا أهم شيء مفقود: التواصل. لن ينجح تعليم الطفل من خلال عرض مقاطع فيديو لا نهاية لها وإجباره على وضع علامة في الاختبارات. هذا يعني أنك بحاجة إما إلى إجراء الدروس بشكل مستقل تمامًا ، أو اختيار إحدى المدارس عبر الإنترنت.



اختيار مدرسة على الإنترنت



لقد عدنا تقريبًا إلى حيث بدأنا. جهاز التحكم؟ حسنًا ، دعنا نلقي نظرة فاحصة عليها. كيف يمكنك تنظيم العملية التعليمية عن بعد؟ هذا يثير العديد من الأسئلة ، سأطرح فقط الأسئلة الرئيسية:



* التواصل المباشر. ماذا تقدم المدارس؟ سكايب ، تيمز في أحسن الأحوال. دروس سكايب؟ هل حقا؟ إذا لم أكن مخطئًا ، فهذا عام 2020. افتح عدة نوافذ بأزرار جميلة متعددة الألوان أمام طالب الصف الأول وانتظر حتى لا يضغط عليها ، ولكن هل سيستمع بطاعة لعمك أو خالتك الممل لمدة نصف يوم؟ لم أر مثل هؤلاء الأطفال من قبل. وأنت أيضا؟



* واجب منزلي. بتعبير أدق ، كيف تصل إلى المعلم للاختبار؟ في الواقع ، هذا سؤال صعب حقًا ، وربما لم يتم حله من حيث المبدأ. الخيارات الموجودة:



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

  2. . , - .

  3. . , .

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

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



* تقديرات. من الواضح أن الدرجات الواردة في الدرس وعند فحص الواجب المنزلي يجب أن تندرج في يوميات إلكترونية متاحة للآباء. ويصلون إلى هناك. لكن ليس على الفور. سألت الأطفال الأكبر سنًا الذين تخرجوا من إحدى المدارس الثانوية المرموقة على شكل قبة ذهبية (ومن المفارقات ، مع تحيز معلوماتي) ، لماذا هذا؟ الجواب ، بصراحة ، فاجأني. اتضح أن المعلمين يكتبون الدرجات على قطعة من الورق ، وبعد الدروس يقودونهم إلى هذه اليوميات الإلكترونية للغاية على بوابة الدولة. وهذا بينما يقوم Tesla Elon Musk بالتصفح في الفضاء



الشاسع ... حسنًا ، حان الوقت لإجراء القليل من البحث التقني والتحقق مما إذا كانت هناك أسباب موضوعية لهذه الحالة؟



دعنا نحدد متطلبات منصة تعلم مثالية افتراضية. في الواقع ، كل شيء بسيط: يجب على الأطفال البقاء في الدرس ، والتركيز على ما يقوله المعلم وعرضه ، والإجابة على الأسئلة إذا لزم الأمر ، ورفع أيديهم إذا رغبوا في ذلك. في الأساس ، نريد نافذة ملء الشاشة بها دفق من كاميرا المعلم أو العرض التقديمي أو السبورة البيضاء. أسهل طريقة لتحقيق ذلك هي استخدام تقنية WebRTC(اتصالات في الوقت الفعلي ، اتصالات في الوقت الفعلي). هذا الشيء يعمل في أي متصفح أكثر أو أقل حديثًا ، ولا يتطلب شراء معدات إضافية ، علاوة على ذلك ، يوفر اتصالًا جيدًا. ونعم ، يتطلب هذا المعيار برمجة غير متزامنة على الأقل لأن طريقة JS المطلوبة navigator.mediaDevices.getUserMedia () تعطي وعدًا . يبدو أن كل شيء واضح ، لقد بدأت في تنفيذه.



استطرادا غنائي حول اختيار الإطار
, «» 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();
})();

      
      







يتم إنشاء خادم الإشارات على إطار عمل Python aiohttp وهو عبارة عن "عرض" بسيط يعمل على إنشاء وكلاء لطلبات WebRTC. يتم الاتصال بالخادم في هذا المثال عبر مآخذ الويب . حسنًا ، بالإضافة إلى ذلك ، يتم إرسال بيانات الدردشة النصية البسيطة عبر قناة الإشارات.



مثال على تنفيذ خادم التشوير
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 ، بالإضافة إلى الاتصال بالفيديو ، منح المتصفح إذنًا لالتقاط محتويات شاشة أو تطبيق منفصل ، وهو ما يمكن أن يكون ضروريًا عند إجراء دروس عبر الإنترنت أو ندوات عبر الإنترنت أو عروض تقديمية. عظيم ، دعونا نستخدمه.



لقد جذبتني الإمكانيات الحديثة للاتصال عبر الفيديو لدرجة أنني نسيت تقريبًا أهم موضوع في الفصل - السبورة التفاعلية. ومع ذلك ، فإن التطبيق الأساسي تافه للغاية لدرجة أنني لن أفرط في تحميل هذه المقالة به. نحن فقط نضيف اللوحة القماشية ، ونستمع إلى أحداث تحريك الفأرة الواحدة (ontouchmove للأجهزة اللوحية) ونرسل الإحداثيات المستلمة إلى جميع النقاط المتصلة عبر نفس خادم الإشارات.



اختبار السبورة التفاعلية



أنت هنا بحاجة إلى جهاز لوحي ومحول رقمي وطفل حي. في نفس الوقت ، سوف نتحقق من إمكانية رقمنة إدخال خط اليد.



بادئ ذي بدء ، أخذت جهازًا لوحيًا قديمًا من نوع Galaxy Tab يعمل بنظام Android 4.4 ، وقلم إلكتروني محلي الصنع وأول دفاتر نسخ صادفتها كخلفية للوحة القماشية. لم أقم بتثبيت برامج إضافية. النتيجة أحبطتني: جهازي اللوحي غير مناسب على الإطلاق للكتابة! أي أنه لا توجد مشكلة في تحريك إصبعك على طوله ، ولكن وضع القلم في مخطط حرف ، حتى لو كان كبيرًا كما في الصورة أدناه ، يمثل مشكلة بالفعل. بالإضافة إلى ذلك ، تبدأ الأداة في التلاشي في عملية الرسم ، ونتيجة لذلك تنكسر الخطوط. بالإضافة إلى ذلك ، لم أتمكن من جعل الطفل لا يريح معصمه على الشاشة ، مما يترك جصًا إضافيًا في متناول اليد ، ويبدأ الجهاز اللوحي نفسه في التباطؤ أكثر. خلاصة القول: الجهاز اللوحي العادي للكتابة على السبورة البيضاء غير مناسب. أقصى إمكانياته هو تحريك إصبعك عبر الشاشة بأشكال كبيرة إلى حد ما. لكن فات الأوان لتقديم هذا لأطفال المدارس.



حسنًا ، هذا بحث نظري بحت ، أليس كذلك؟ ثم نأخذ جهاز التحويل الرقمي (المعروف أيضًا باسم الكمبيوتر اللوحي الرسومي) بتنسيق Wacom Bamboo A8 ، ونشاهد الطفل.



لاحظ أن موضوع الاختبار البالغ من العمر ست سنوات حصل على كمبيوتر محمول بقلم رسومي لأول مرة في حياته. استغرق الأمر منا عشر دقائق للحصول على المهارات الأساسية لاستخدام القلم ، وفي الدرس الثاني بالفعل ، استخدم الطفل الجهاز اللوحي بثقة تامة ، وتم مسحه بشكل مستقل من اللوحة ، ورسم الوجوه ، والزهور ، وكلبنا ، وحتى بدأ في الضغط على الأزرار المتوفرة في إبسيلون ، وطرح أسئلة مثل "لماذا يرفعون أيديهم في المدرسة؟" لكن النتيجة تركت دائمًا الكثير مما هو مرغوب فيه. الحقيقة هي أن المصممين والفنانين يقومون بتكبير جزء من الصورة لرسم عنصر ، مما يجعل الخطوط دقيقة. هنا يجب أن نرى اللوحة بأكملها ، بمقياس 1: 1. هنا والكبار لن يقعوا في الخط. هذا ما حصلنا عليه:



صورة



الحكم النهائي: لا يوجد خط يد غير وارد. وإذا أردنا "مساعدة" أطفالنا ، فنحن بحاجة إلى تحقيق ذلك بأنفسنا ، على الورق ، فلن تساعد المدرسة في ذلك.



يجب أن أقول إن الطفل أخذ كل تجاربي بحماس ، علاوة على ذلك ، منذ ذلك الحين كان يتابعني بذيله ويطلب مني "تشغيل الوصفات". حسنًا بالفعل ، ستكون المهارة المكتسبة مفيدة له ، فقط لأغراض مختلفة تمامًا.



على أي حال ، كنتيجة للتجارب ، حصلت بالفعل على MVP - منتج قابل للتطبيق على الأقل تقريبًامناسب للدروس عبر الإنترنت ، مع مؤتمر فيديو / صوتي ، وشاشة مشتركة ، ولوح أبيض تفاعلي ، ودردشة نصية بسيطة وزر رفع يدوي. هذا في حالة عدم وجود ميكروفون لدى الطفل فجأة. نعم يحدث هذا خاصة بين الأطفال الذين لم يتعلموا دروسهم.



لكن في برميل العسل هذا ، لسوء الحظ ، هناك بضع ملاعق من القطران.



اختبار WebRTC



عدد الملعقة 1. نظرًا لأن اتصالات الفيديو الخاصة بنا تستخدم اتصالات مباشرة بين العملاء ، فإن الخطوة الأولى هي اختبار قابلية هذا الحل للتوسع. للاختبار ، أخذت جهاز كمبيوتر محمولًا قديمًا به معالج i5-3230M ثنائي النواة على متن الطائرة ، وبدأت في توصيل العملاء بكاميرات الويب المعطلة به ، أي محاكاة وضع واحد إلى متعدد:



صورة



كما ترى ، فإن الكمبيوتر المحمول التجريبي قادر على البث بشكل مريح أكثر أو أقل لخمسة عملاء ( عند تحميل وحدة المعالجة المركزية في حدود 60٪). ويتم توفير ذلك ، يتم تقليل دقة دفق الفيديو الصادر إلى 720 بكسل (640 × 480 بكسل) ومعدل الإطارات إلى 15 إطارًا في الثانية. من حيث المبدأ ، ليس سيئًا للغاية ، ولكن عند الاتصال بفصل مكون من عدة عشرات من الطلاب ، سيتعين عليك التخلي عن "الشبكة الكاملة" لصالح التتابع ، أي أن كل عميل من العملاء الخمسة الأوائل يربط الدفق بالخمسة التالية ، وهكذا.



عدد الملعقة 2.لإنشاء اتصال تفاعلي مباشر (ICE) بين العملاء ، يحتاجون إلى تجاوز جدران الحماية و NATs. للقيام بذلك ، يستخدم WebRTC خادم STUN ، والذي يُعلم العملاء بمعلمات الاتصال الخارجية. ويعتقد أن هذا يكفي في معظم الحالات. لكنني كنت "محظوظًا" على الفور تقريبًا:



كما ترى ، يشكو مصحح الأخطاء من استحالة اتصال ICE ويتطلب اتصال خادم TURN ، أي مرحل. وهذا باهظ الثمن بالفعل. لا غنى عن خادم إشارات بسيط. الخلاصة - عليك تمرير جميع التدفقات عبر خادم الوسائط.



وسائل الاعلام الخادم



للاختبار ، استخدمت aiortc . تطور مثير للاهتمام ، يسمح لك بتوصيل المتصفح مباشرة بالخادم عبر WebRTC. ليست هناك حاجة إلى إشارات منفصلة ، يمكنك استخدام قناة البيانات الخاصة بالاتصال نفسه. يعمل هذا ، جميع نقاط الاختبار الخاصة بي متصلة بدون مشكلة. لكن المشكلة مع الأداء. صدى بسيط لدفق فيديو / صوت بنفس حدود 720 بكسل و 15 إطارًا في الثانية أكل 50٪ من وحدة المعالجة المركزية الافتراضية الخاصة بي في اختبار VDS. علاوة على ذلك ، إذا تم زيادة التحميل إلى 100٪ ، فلن يكون لدفق الفيديو وقت لتفريغه للعملاء ويبدأ في انسداد الذاكرة ، مما يؤدي في النهاية إلى توقف الخادم. من الواضح أن لغة Python التي نحب استخدامها في معالجة الإدخال / الإخراج ليست مرتبطة جدًا بوحدة المعالجة المركزية. سيتعين علينا البحث عن حل أكثر تخصصًا ، على سبيل المثال ، Janusأو جيتسي .



على أي حال ، سيتطلب الحل الكامل خوادم مخصصة ، وفقًا لتقديراتي ، بمعدل أساسي واحد لكل غرفة (فئة). هذا بالفعل يكلف مالًا ويتجاوز الاختبارات البسيطة ، لذلك في هذه المرحلة سأفكر في اكتمال المرحلة الأولى من بحثي.



الاستنتاجات



1. بعبارة ملطفة ، من الغريب رؤية إرشادات التنزيل والروابط للتسجيل في برنامج العدو المحتمل السابق رقم 1 (هنا حول Microsoft Teams) على البوابة الرسمية للاتحاد الروسي . وهذا في عصر العقوبات واستبدال الواردات.

لا ، أنا شخصياً أؤيد صداقة الشعوب ، وبشكل عام ، كل أنواع التسامح ، لكن هل حقًا أنا فقط من هذا "التكامل" الذي يقف شعري في النهاية؟ أليست هناك تطورات لدينا؟



2. تكامل MES / NES مع المدارس. في الواقع ، مطورو MES رائعون ، حتى أنهم قاموا بالتكامل مع Yandex.tutor. ماذا عن الدرجات في الوقت الفعلي أثناء الدروس ، متى سيكون هناك API؟ أم لست على علم بشيء؟



3. عند اختيار شكل من أشكال التعليم عن بعد أو الأسرة ، يجب أن تكون صادقًا مع نفسك: لن تكون قادرًا على نقل مسؤولية تعليم طفلك إلى المدرسة. يقع كل عمل إجراء الدروس (في حالة التربية الأسرية) ، والحفاظ على الانضباط والتنظيم الذاتي (في أي حال) على عاتق الوالدين بالكامل. يجب أن تكون على دراية بهذا وأن تجد وقتًا للفصول الدراسية. ومع ذلك ، لا ينبغي أن يكون هذا مشكلة في العائلات الكبيرة.



4. لن أقوم بتضمين روابط لمدارس مختارة عبر الإنترنت هنا ، حتى لا يتم اعتبارها إعلانًا. سأقول فقط أننا اخترنا مدارس خاصة من النطاق السعري المتوسط. على أي حال ، ستعتمد النتيجة النهائية على الطفل ولن نتسلمها قبل شهر سبتمبر.



أم أنه من المنطقي نقل التطوير الذي بدأ هنا إلى نهايته المنطقية وتنظيم مدرستك الخاصة؟ ما رأيك؟ هل هناك أشخاص متشابهون في التفكير لديهم معرفة وخبرة متخصصة في مجال التعليم؟



روابط مفيدة:

المدرسة الإلكترونية الروسية مدرسة

موسكو الإلكترونية

مكتبة MES

للمطورين



All Articles