ç§ã¯ããŠã³ã·ãã¿ãŒã§ããããŸããŸãéå»3幎éã劻ãšç§ã¯çªã®å€ã®ç°å颚æ¯ãæ°é®®ãªç©ºæ°ãé³¥ã®ãããããæ¥œããã§ããŸãããå®¶ã®äžã®äŸ¿å©ããå°å ã®ãããã€ããŒããã®å ã€ã³ã¿ãŒãããã匷åãªäžæã®ãªã黿ºããããŠçªç¶çŸããã³ãããã¯ãçªç¶ã倧éœåžããç§»åãããšããèããããã»ã©å¥åŠã§ã¯ãããŸããã§ããã
ç§ãç±å¿ã«ãŠã§ãéçºã«æºãã£ãŠããéãããã¯ã°ã©ãŠã³ãã®ã©ããã§ã劻ã¯ç§ã®åäŸã®ããã®åŠæ ¡ãéžã¶ããšã®åé¡ã«ã€ããŠå®æçã«äžå¹³ãèšããŸããããããŠïŒçªç¶ïŒåäŸã¯æé·ããåŠæ ¡ã®è³ªåã¯çŽç«ããŸãããããŠããã®æãæ¥ãŸãããäžç·ã«ãããçè§£ããŸããããåå°ã®åã®1/6ã®æè²ã·ã¹ãã ã®äœãåé¡ã«ãªã£ãŠããŸããããããŠããã«ã€ããŠç§ãã¡ã¯äœãã§ããŸããïŒ
åŸæ¥ã®å¯Ÿé¢æå°æ¹æ³ã¯ããã®èšäºã®ç¯å²å€ã«ããŠãããŸããæ®éã®åŠæ ¡ã«ã¯ãè°è«ã®äœå°ã®ãªãé·æãšé倧ãªçæã®äž¡æ¹ããããŸãããã¡ãªã¿ã«ãæè¿ã匷å¶çãªèªå·±éé¢ã远å ãããŠããŸããããã§ã¯ãããŸããŸãªçç±ã§æè¿ãŸããŸãå€ãã®èŠªãåŒãä»ããŠããè·é¢ãšå®¶æã®æè²ãªãã·ã§ã³ãèŠãŠãããŸãã
æç¢ºã«èšããšãè·é¢åŠç¿ãšã¯ãè·é¢åŠç¿æè¡ãïŒDOTïŒã䜿çšããéåžžã®åŠæ ¡ã§ã®ææ¥ãæå³ããå®¶ææè²ãšã¯èªçºçã«åŠæ ¡ãèŸããŠå®¶æã ãã§åŠç¿ããããšãæå³ããŸãïŒå®éãããã¯å€ãè¯ãå€äº€ã§ãïŒããã ãããããã®å Žåããå°ãªããšãäžéèªå®ã«åæ Œããããã«ã¯ãåäŸã¯å©çšå¯èœãªåŠæ ¡ã®ããããã«æå±ããŠããå¿ èŠããããŸãã
ãããŠä»ã人çããã®ããã€ãã®èгå¯ããã§ã«æ®éã®åŠæ ¡ã§å匷ããŠããåäŸãã¡ã®é éåŠç¿ãžã®åŒ·å¶ç§»ç±ã§ããã¹ãŠãæ²ããã§ããåŠç«¥ã¯ãã®éåœã®è³ç©ãäžçš®ã®äŒæãšããŠèªèããäž¡èŠªã¯ææ¥äžã«èŠåŸã«åŸãããšã«æ £ããŠãããããã®çµæãå šäœçãªåŠæ¥æçžŸã¯å¿ ç¶çã«äœäžããŸãã
äžå¹Žçã®å Žåãç¹ã«å®¶æã®å Žåã䞡芪ã¯ãããããèªç¶ãªèå³ãšç®æ°ããã®å¹æãå©çšããŠãåäŸããã¬ãŒã«ã«ä¹ãããæ©äŒããããŸããå人çã«ã¯ãèªç«ããããšãäž»ãªä»äºã§ããåäŸãšäžç·ã«åº§ã£ãŠå®¿é¡ãããŠãããšã
èŠç¹ãã€ãããå ¬ç«åŠæ ¡ã®éžæ
ãã¶ããããã°ã©ã ããã¬ãŒãã³ã°ã¹ã±ãžã¥ãŒã«ãéžã¶æ©äŒãããã®ã§ãå®¶ææè²ããã£ãšå¥œãã§ãããããŠãããªãã¯ç©ççã«åŠæ ¡ã«éãé »åºŠãæžããããšãã§ããŸãããã ãã9æã«é©ãããªãããã«ãå ¬ç«åŠæ ¡ãéžã³ãç£ç£ã«åäŸã®é 眮ã«ã€ããŠè©±ããå¬ã®çµããã«1幎çãžã®å ¥åŠãåœããå¿ èŠããããŸããæ³çãªèгç¹ããã¯ãæè²æ³ã¯æ¯å¹Žã®èšŒæãèŠæ±ããŠããªãããã§ãããç§ã®çµéšã§ã¯ããç· ãåããã¯åªããåæ©ä»ãã§ããããã蚌æãããããã«ããŸããã©ã®åŠæ ¡ãäž¡æãåºããŠç§ãã¡ãåãå ¥ããå¯èœæ§ã¯äœãã§ãããæå¯ãã®éœåžã§äŸ¡å€ã®ããéžæè¢ãèŠã€ããããšãã§ãããšç¢ºä¿¡ããŠããŸãã
ã«ãªãã¥ã©ã ã®éžæ
æ£ç¢ºã«éžæããŸããå°éçãªæè²ãåããã«èªåã§ããã°ã©ã ãäœãããšããã®ã¯åççã§ã¯ãããŸããããã·ã¢é»ååŠæ ¡ïŒNESïŒãã¢ã¹ã¯ã¯é»ååŠæ ¡ïŒ MESïŒãªã©ã®æ¿åºã®æè²ãªãœãŒã¹ããããŸãã ãçè«çã«ã¯ããã§ååã§ãã...ã©ã¡ãã®ãªãã·ã§ã³ã§ããã¬ãã¹ã³ãã©ã³ããããªããã¹ãããã¥ãŒããªã¢ã«ãæäŸãããŸããç§ãèŠã€ããããªãã£ãã®ã¯ã匷å¶çãªã«ãªãã¥ã©ã ã§ãããæç§æžãã®ãã®ã§ããã
ãããŠããã§æãéèŠãªããšã¯æ¬ ããŠããŸãïŒã³ãã¥ãã±ãŒã·ã§ã³ãç¡éã®ãããªãèŠããŠåäŸã«ãã¹ãããã§ãã¯ãããããšã§åäŸãæããããšã¯ããŸããããŸãããã€ãŸããå®å šã«ç¬ç«ããŠã¬ãã¹ã³ã宿œãããããªã³ã©ã€ã³ã¹ã¯ãŒã«ã®1ã€ãéžæããå¿ èŠããããŸãã
ãªã³ã©ã€ã³ã¹ã¯ãŒã«ã®éžæ
å§ãããšããã«ã»ãŒæ»ã£ãŠããŸãããªã¢ã³ã³ïŒããŠã圌女ã詳ããèŠãŠã¿ãŸããããæè²ããã»ã¹ããªã¢ãŒãã§ã©ã®ããã«æŽçã§ããŸããïŒããã¯å€ãã®è³ªåãæèµ·ããŸããç§ã¯éèŠãªãã®ã ããæèµ·ããŸãïŒ
*ã©ã€ãã³ãã¥ãã±ãŒã·ã§ã³ãåŠæ ¡ã¯äœãæäŸããŠããŸããïŒã¹ã«ã€ãããããããã£ã ã¹ã Skypeã¬ãã¹ã³ïŒæ¬åœã«ïŒç§ãééã£ãŠããªããã°ã2020幎ã§ããäžå¹Žçã®åã§çŸãããã«ãã«ã©ãŒã®ãã¿ã³ãä»ããããã€ãã®ãŠã£ã³ããŠãéãã圌ãããããæŒããªãã®ãåŸ ã¡ãŸãããéå±ãªåç¶ã忝ã«åæ¥çŽ çŽã«è³ãåŸããŸããïŒç§ã¯ãã®ãããªåäŸãèŠãããšããããŸãããããªããïŒ
* 宿é¡ãããæ£ç¢ºã«ã¯ãããã¯ã©ã®ããã«ããŠãã¹ãã®ããã«æåž«ã«å±ããŸããïŒå®éãããã¯æ¬åœã«é£ãã質åã§ãããããããååçã«ã¯è§£æ±ºãããŠããŸãããæ¢åã®ãªãã·ã§ã³ïŒ
- , . --, , , , - .
- . , - .
- . , .
- . , , , , ? . , , .
- . , , . , , , . , . . , , , , . .
* èŠç©ããæããã«ãã¬ãã¹ã³ã§äžããããæçžŸãšå®¿é¡ããã§ãã¯ãããšãã¯ã䞡芪ãå©çšã§ããé»åæ¥èšã«åé¡ãããã¹ãã§ãããããŠåœŒãã¯ããã«çããŸããããããããã«ã§ã¯ãããŸãããéã®ããŒã ã®ããäžæµã®ãªã»ãŠã ã®1ã€ã忥ãã幎é·ã®åäŸãã¡ã«å°ããŸããïŒç®èãªããšã«ãæ å ±ã®åãããããŸãïŒãããã¯ãªãã§ããïŒãã®çãã¯ãæ£çŽãç§ãé©ãããŸãããæåž«ã¯äžæã®çŽã«æçžŸãæžãçããã¬ãã¹ã³ã®åŸãå·ã®ããŒã¿ã«ã§ãã®éåžžã«é»åçãªæ¥èšã«ããããé転ããããšãããããŸããããããŠãããã¯ãã¹ã©ãšãã³ã ã¹ã¯ãåºå€§ãªã¹ããŒã¹ãèããŠããé...
ããŠãå°ãæè¡çãªèª¿æ»ãããŠããã®ç¶æ³ã«å®¢èгçãªçç±ããããã©ããã確èªããæãæ¥ãŸãããïŒ
æ¶ç©ºã®çæ³çãªåŠç¿ãã©ãããã©ãŒã ã®èŠä»¶ãå®çŸ©ããŸããããå®éããã¹ãŠãåçŽã§ããåäŸãã¡ã¯ã¬ãã¹ã³ã«ãšã©ãŸããæåž«ã®èšãããšãšèŠããããšã«çŠç¹ãåãããå¿ èŠã«å¿ããŠè³ªåã«çããå¿ èŠã«å¿ããŠæãäžããå¿ èŠããããŸããåºæ¬çã«ãæåž«ã®ã«ã¡ã©ããã¬ãŒã³ããŒã·ã§ã³ããŸãã¯ãã¯ã€ãããŒãããã®ã¹ããªãŒã ãå«ããã«ã¹ã¯ãªãŒã³ãŠã£ã³ããŠãå¿ èŠã§ãããããå®çŸããæãç°¡åãªæ¹æ³ã¯ãWebRTCãã¯ãããžãŒã䜿çšããããšã§ãã ïŒãªã¢ã«ã¿ã€ã éä¿¡ããªã¢ã«ã¿ã€ã éä¿¡ïŒãããã¯ãå€ããå°ãªããææ°ã®ãã©ãŠã¶ã§æ©èœãã远å ã®æ©åšãè³Œå ¥ããå¿ èŠããªããããã«ãé«åè³ªã®æ¥ç¶ãæäŸããŸãããããŠãã¯ããå°ãªããšãå¿ èŠãªJSã¡ãœããnavigator.mediaDevices.getUserMediaïŒïŒãpromiseãè¿ãããããã®æšæºã§ã¯éåæããã°ã©ãã³ã°ãå¿ èŠã§ã ããã¹ãŠãæç¢ºãªããã§ããç§ã¯ãããå®è£ ãå§ããŠããŸãã
ãã¬ãŒã ã¯ãŒã¯ã®éžæã«ã€ããŠã®åæ
çãªéžè±
, «» JavaScript , .
jQuery. , JS :
, CSS «hidden», , opacity transition, fadeIn/fadeOut CSS. , JS !
. .. , JS , . , , «» 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ã©ã€ãã©ãª