教育向けビデオ ストリーミング: WebRTC vs HLS vs DASH
ビデオは現代のデジタル教育の中心です。大学の授業の生放送より Coursera オンデマンド ライブラリへ、ビデオ通話による個別指導セッションから使用した MOOC へ 電車内でのオフラインでは、各シナリオで大きく異なる技術的ニーズがあります。プロトコルを選択してください 間違っているということは、ライブセッションで耐えられない遅延が発生し、学生にとって過剰なバッファリングが発生することを意味します 帯域幅が制限されているか、同時ユーザーが 100 人を超えて拡張できないアーキテクチャ。
2025 年、教育プラットフォームは視聴者の二極化というさらなる課題に直面します。 一方では、シームレスな 4K ビデオを期待する高帯域幅環境の学生。 一方、ユネスコによると、依然として世界の学生の 37% がデジタル教育にアクセスしています 接続速度が 1 Mbps 未満の場合。最初のグループ専用に設計されたビデオ アーキテクチャ 2番目は体系的に除外されます。
この記事では、3 つの主要なプロトコルについて詳しく説明します。 WebRTC, HLS e MPEG-ダッシュ、実際の教育シナリオ向け、実装あり 具体的なパターンとパターンを使用して、すべてのユースケースをカバーするハイブリッド アーキテクチャを構築します。
何を学ぶか
- WebRTC の技術アーキテクチャ: ICE、STUN、TURN、SDP、メディア パイプライン
- HLS と DASH: 適応セグメンテーション、マニフェスト ファイル、CDN 配布
- 実際の比較: 遅延、スケーラビリティ、デバイスのサポート、DRM
- ハイブリッド アーキテクチャ: ライブには WebRTC、VOD には HLS/DASH
- 限られた帯域幅の最適化: ABR、コーデック選択、プリロード
- React と HLS.js を使用した教育プレーヤーの実装
1. WebRTC: ライブレッスンのためのリアルタイム通信
WebRTC (ウェブリアルタイム通信) および通信用の W3C 標準 プラグインなしでブラウザーでピアツーピアのオーディオ/ビデオを再生できます。レイテンシは 200 ~ 500 ミリ秒 (6 ~ 30 秒) HLS)、本物のライブ インタラクションには WebRTC が唯一の選択肢です: リアルタイム Q&A 付きのレッスン、 1対1の個別指導セッション、インタラクティブな仮想ワークショップ。
WebRTC の複雑さは、シグナリングと NAT トラバーサルにあります。 2 つのピアが接続しない 直接: 最初にシグナリング サーバー経由で接続情報を交換する必要があります。 次に、STUN/TURN を使用してホームおよび企業の NAT を通過します。
// Server WebRTC Signaling con Socket.io
// Gestisce lo scambio di SDP offer/answer e ICE candidates
import express from 'express';
import { createServer } from 'http';
import { Server as SocketServer } from 'socket.io';
interface Room {
hostSocketId: string;
participants: Set<string>;
maxParticipants: number;
}
const app = express();
const httpServer = createServer(app);
const io = new SocketServer(httpServer, {
cors: { origin: process.env.CORS_ORIGIN || '*' }
});
const rooms = new Map<string, Room>();
io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
// --- JOIN ROOM ---
socket.on('join-room', (data: { roomId: string; role: 'host' | 'student' }) => {
const { roomId, role } = data;
if (!rooms.has(roomId)) {
if (role !== 'host') {
socket.emit('error', { message: 'Room not found' });
return;
}
rooms.set(roomId, {
hostSocketId: socket.id,
participants: new Set(),
maxParticipants: 200 // Limite per WebRTC server-side
});
}
const room = rooms.get(roomId)!;
if (room.participants.size >= room.maxParticipants) {
socket.emit('error', { message: 'Room full - join via HLS fallback' });
return;
}
socket.join(roomId);
room.participants.add(socket.id);
// Notifica host dell'ingresso nuovo studente
if (role === 'student') {
io.to(room.hostSocketId).emit('student-joined', {
studentId: socket.id,
participantCount: room.participants.size
});
}
socket.emit('room-joined', {
roomId,
hostSocketId: room.hostSocketId,
participantCount: room.participants.size
});
});
// --- WEBRTC SIGNALING ---
// Lo schema SFU (Selective Forwarding Unit) e preferito per classi > 4 persone
socket.on('offer', (data: { targetId: string; sdp: RTCSessionDescriptionInit }) => {
// Forwarda SDP offer al peer target
io.to(data.targetId).emit('offer', {
fromId: socket.id,
sdp: data.sdp
});
});
socket.on('answer', (data: { targetId: string; sdp: RTCSessionDescriptionInit }) => {
io.to(data.targetId).emit('answer', {
fromId: socket.id,
sdp: data.sdp
});
});
socket.on('ice-candidate', (data: { targetId: string; candidate: RTCIceCandidateInit }) => {
io.to(data.targetId).emit('ice-candidate', {
fromId: socket.id,
candidate: data.candidate
});
});
socket.on('disconnect', () => {
// Cleanup room se l'host disconnette
for (const [roomId, room] of rooms.entries()) {
room.participants.delete(socket.id);
if (room.hostSocketId === socket.id) {
io.to(roomId).emit('host-disconnected');
rooms.delete(roomId);
}
}
});
});
httpServer.listen(3001, () => console.log('Signaling server on :3001'));
// Client WebRTC - Classe per gestire la connessione dal lato studente
// Gestisce STUN/TURN, media capture e reconnection automatica
class EdTechWebRTCClient {
private peerConnection: RTCPeerConnection | null = null;
private localStream: MediaStream | null = null;
private remoteStream: MediaStream | null = null;
private readonly iceServers: RTCIceServer[] = [
// STUN pubblici Google (gratuiti)
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
// TURN server (necessario per NAT simmetrico ~15% degli utenti)
{
urls: 'turn:turn.youredtech.com:3478',
username: process.env.TURN_USERNAME!,
credential: process.env.TURN_CREDENTIAL!
}
];
constructor(
private signalingSocket: Socket,
private onRemoteStream: (stream: MediaStream) => void
) {
this.setupSignalingHandlers();
}
async joinAsStudent(roomId: string): Promise<void> {
// Crea PeerConnection
this.peerConnection = new RTCPeerConnection({
iceServers: this.iceServers,
iceTransportPolicy: 'all', // Usa 'relay' per forzare TURN in ambienti restrittivi
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require'
});
// Handler per stream remoto (video del docente)
this.peerConnection.ontrack = (event) => {
if (event.streams[0]) {
this.remoteStream = event.streams[0];
this.onRemoteStream(event.streams[0]);
}
};
// Handler per ICE candidates
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.signalingSocket.emit('ice-candidate', {
targetId: 'host', // Semplificato - in prod usa l'ID reale
candidate: event.candidate.toJSON()
});
}
};
// Monitoraggio qualità connessione
this.peerConnection.onconnectionstatechange = () => {
const state = this.peerConnection?.connectionState;
console.log(`Connection state: ${state}`);
if (state === 'failed') {
this.handleConnectionFailure();
}
};
this.signalingSocket.emit('join-room', { roomId, role: 'student' });
}
private async handleConnectionFailure(): Promise<void> {
console.warn('WebRTC connection failed - attempting ICE restart');
try {
// ICE restart: rinegozia i candidati senza riavviare tutta la sessione
const offer = await this.peerConnection!.createOffer({ iceRestart: true });
await this.peerConnection!.setLocalDescription(offer);
this.signalingSocket.emit('offer', { sdp: offer });
} catch (err) {
// Fallback a HLS se WebRTC non recupera
console.error('ICE restart failed, switching to HLS fallback');
this.switchToHLSFallback();
}
}
private switchToHLSFallback(): void {
// Evento custom per far switchare il player a HLS
window.dispatchEvent(new CustomEvent('webrtc-fallback', {
detail: { hlsUrl: `${process.env.STREAM_CDN_URL}/live/stream.m3u8` }
}));
}
private setupSignalingHandlers(): void {
this.signalingSocket.on('offer', async (data: { sdp: RTCSessionDescriptionInit }) => {
await this.peerConnection!.setRemoteDescription(data.sdp);
const answer = await this.peerConnection!.createAnswer();
await this.peerConnection!.setLocalDescription(answer);
this.signalingSocket.emit('answer', { sdp: answer });
});
this.signalingSocket.on('ice-candidate', async (data: { candidate: RTCIceCandidateInit }) => {
await this.peerConnection!.addIceCandidate(data.candidate);
});
}
async getConnectionStats(): Promise<object> {
if (!this.peerConnection) return {};
const stats = await this.peerConnection.getStats();
const result: Record<string, unknown> = {};
stats.forEach((report) => {
if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
result.packetsReceived = report.packetsReceived;
result.packetsLost = report.packetsLost;
result.jitter = report.jitter;
result.framesPerSecond = report.framesPerSecond;
result.bytesReceived = report.bytesReceived;
}
});
return result;
}
}
2. HLS: オンデマンド コンテンツの標準
HTTP ライブ ストリーミング (HLS)Apple によって開発され、IETF によって標準化されました。 ビデオ オンデマンドと大規模なライブ ストリーミングの主要なプロトコルです。彼の強さ 特殊なインフラストラクチャを使用せずに任意の HTTP サーバーまたは CDN 上で実行することにあります。 すべてのデバイスとの普遍的な互換性、アダプティブ ビットレート ストリーミング (ABR) これにより、すべての接続で可能な限り最高の品質が保証されます。
# Pipeline FFmpeg per generare HLS multi-bitrate
# Crea 4 rappresentazioni (1080p, 720p, 480p, 360p) + master playlist
import subprocess
import os
from pathlib import Path
def create_hls_vod(
input_file: str,
output_dir: str,
segment_duration: int = 4
) -> str:
"""
Converte un video in formato HLS multi-bitrate per VOD educativo.
Restituisce il path del master manifest M3U8.
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
# Ladder di qualità per EdTech:
# 1080p per laboratori e presentazioni dettagliate
# 720p per lezioni standard
# 480p per connessioni mobili moderate
# 360p per connessioni lente (UNESCO: 37% studenti < 1Mbps)
quality_ladder = [
{'height': 1080, 'bitrate': 3000, 'maxrate': 3200, 'bufsize': 6000},
{'height': 720, 'bitrate': 1500, 'maxrate': 1600, 'bufsize': 3000},
{'height': 480, 'bitrate': 800, 'maxrate': 856, 'bufsize': 1600},
{'height': 360, 'bitrate': 400, 'maxrate': 428, 'bufsize': 800},
]
# Costruisce il comando FFmpeg
cmd = ['ffmpeg', '-i', input_file, '-preset', 'slow']
maps = []
var_stream_map = []
for i, q in enumerate(quality_ladder):
# Video stream per ogni qualità
cmd += [
'-map', '0:v',
f'-c:v:{i}', 'libx264',
f'-b:v:{i}', f'{q["bitrate"]}k',
f'-maxrate:v:{i}', f'{q["maxrate"]}k',
f'-bufsize:v:{i}', f'{q["bufsize"]}k',
f'-vf:v:{i}', f'scale=-2:{q["height"]}',
f'-profile:v:{i}', 'high',
]
var_stream_map.append(f'v:{i},a:{i}')
# Audio unificato
cmd += ['-map', '0:a']
for i in range(len(quality_ladder)):
cmd += [f'-c:a:{i}', 'aac', f'-b:a:{i}', '128k', f'-ac:{i}', '2']
# HLS output
cmd += [
'-f', 'hls',
'-hls_time', str(segment_duration),
'-hls_playlist_type', 'vod',
'-hls_flags', 'independent_segments',
'-hls_segment_type', 'mpegts',
'-hls_segment_filename', f'{output_dir}/stream_%v/segment_%03d.ts',
'-master_pl_name', 'master.m3u8',
'-var_stream_map', ' '.join(var_stream_map),
f'{output_dir}/stream_%v/playlist.m3u8'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"FFmpeg failed: {result.stderr}")
# Aggiungi metadata educativi al master manifest
master_path = os.path.join(output_dir, 'master.m3u8')
_inject_metadata(master_path, quality_ladder)
return master_path
def _inject_metadata(manifest_path: str, quality_ladder: list) -> None:
"""
Inietta metadata nel manifest HLS per player educativo.
Aggiunge BANDWIDTH, RESOLUTION e FRAME-RATE.
"""
with open(manifest_path, 'r') as f:
content = f.read()
# Il master.m3u8 generato da FFmpeg e già corretto
# Qui potremmo aggiungere tag #EXT-X-SESSION-DATA per metadata del corso
enhanced = content.replace(
'#EXTM3U',
'#EXTM3U\n#EXT-X-SESSION-DATA:DATA-ID="course.chapter",VALUE="1"\n'
'#EXT-X-SESSION-DATA:DATA-ID="course.duration",VALUE="3600"'
)
with open(manifest_path, 'w') as f:
f.write(enhanced)
# Player React con HLS.js e Chapter Navigation
# Implementa feature specifiche per EdTech: capitoli, velocità, note
const EdTechHLSPlayer_CODE = `
import Hls from 'hls.js';
import { useEffect, useRef, useState, useCallback } from 'react';
interface Chapter {
time: number;
title: string;
thumbnail?: string;
}
interface PlayerProps {
src: string;
chapters?: Chapter[];
onProgress?: (time: number) => void;
onComplete?: () => void;
}
export function EdTechPlayer({ src, chapters = [], onProgress, onComplete }: PlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const hlsRef = useRef<Hls | null>(null);
const [currentLevel, setCurrentLevel] = useState(-1); // -1 = auto
const [isBuffering, setIsBuffering] = useState(false);
const [watchedSegments, setWatchedSegments] = useState<Set<number>>(new Set());
useEffect(() => {
const video = videoRef.current;
if (!video) return;
if (Hls.isSupported()) {
const hls = new Hls({
// Configurazione ottimizzata per EdTech
startLevel: -1, // Auto quality selection
capLevelToPlayerSize: true, // Non scaricare qualità > viewport
maxBufferLength: 60, // Buffer 60s per buffering predittivo
maxMaxBufferLength: 120,
lowLatencyMode: false, // Non necessario per VOD
progressive: true, // Inizia playback prima del download completo
// Timeout generosi per connessioni lente
manifestLoadingTimeOut: 15000,
levelLoadingTimeOut: 15000,
fragLoadingTimeOut: 30000,
// ABR aggressivo: scendi di qualità rapidamente su congestione
abrEwmaFastLive: 3.0,
abrEwmaSlowLive: 9.0,
abrBandWidthFactor: 0.8,
});
hls.loadSource(src);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play().catch(() => {}); // Gestisce autoplay policy
});
hls.on(Hls.Events.LEVEL_SWITCHED, (_, data) => {
setCurrentLevel(data.level);
});
hls.on(Hls.Events.BUFFER_STALLED_ERROR, () => setIsBuffering(true));
hls.on(Hls.Events.BUFFER_FLUSHED, () => setIsBuffering(false));
hlsRef.current = hls;
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari nativo HLS
video.src = src;
}
return () => hlsRef.current?.destroy();
}, [src]);
// Tracciamento progresso per analytics
const handleTimeUpdate = useCallback(() => {
const video = videoRef.current;
if (!video) return;
const currentTime = video.currentTime;
onProgress?.(currentTime);
// Marca segmento come visto (per completion tracking)
const segmentIndex = Math.floor(currentTime / 30); // Segmenti da 30s
setWatchedSegments(prev => new Set([...prev, segmentIndex]));
// Completion: considera completato a 90% del video
if (currentTime / video.duration > 0.9) {
onComplete?.();
}
}, [onProgress, onComplete]);
return (
<div className="edtech-player">
<video
ref={videoRef}
onTimeUpdate={handleTimeUpdate}
controls
playsInline
/>
{isBuffering && <div className="buffering-indicator">Buffering...</div>}
<div className="chapter-navigation">
{chapters.map((ch, i) => (
<button
key={i}
onClick={() => { if (videoRef.current) videoRef.current.currentTime = ch.time; }}
>
{ch.title}
</button>
))}
</div>
</div>
);
}
`;
3. MPEG-DASH: オープンスタンダード
MPEG-DASH (HTTP を介したダイナミック アダプティブ ストリーミング) および同等の ISO 規格
HLS に準拠しますが、完全にオープンでコーデックに依存しません。 DASH はファイルを使用します .mpd (メディアプレゼンテーション)
説明)ポスターの代わりに .m3u8 HLSの。 HLS と DASH の主な選択
現在、主に以下によって決定されます。
| 特性 | HLS | MPEG-ダッシュ |
|---|---|---|
| ネイティブSafari/iOS | はい(ネイティブ) | いいえ (dash.js が必要) |
| ネイティブ Chrome/Firefox | いいえ (hls.js が必要) | 部分的 (EME/MSE) |
| コーデックのサポート | H.264、H.265、VP9 | 任意 (不可知論的) |
| DRM (ワイドバイン/フェアプレイ) | FairPlay (Apple)、HLS+ 経由 | Widevine、ネイティブ PlayReady |
| 最小セグメント期間 | 2 秒 (4 ~ 6 秒を推奨) | 1秒(1秒未満の可能性あり) |
| ライブレイテンシ | LL-HLS:2~3秒、標準:6~30秒 | LL-ダッシュ: 1-2秒 |
4. 完全な EdTech プラットフォームのためのハイブリッド アーキテクチャ
完全な教育プラットフォームのための最適なソリューションは、3 つのプロトコルを 1 つに結合します。 自動フォールバックを備えたハイブリッド アーキテクチャ:
- インタラクティブなライブレッスン (生徒数 50 人未満): WebRTC メッシュ/SFU による遅延を最小限に抑える
- ライブブロードキャストレッスン (生徒数 50 人以上): WebRTC パブリッシュ + メディア サーバー経由の HLS/DASH エグレス
- VODコンテンツ: CDN 上のマルチビットレート HLS (CloudFront、Fastly)
- インタラクティブ性を備えたリプレイ: HLS + WebSocket による同期 Q&A
# Configurazione Nginx per Media Server ibrido
# Pattern: WebRTC -> RTMP -> HLS/DASH (via Nginx-RTMP module)
# nginx.conf
events {
worker_connections 4096;
}
http {
# Configurazione CORS per CDN distribution
map $http_origin $cors_origin {
default "";
~^https://.*\.youredtech\.com$ $http_origin;
}
server {
listen 8080;
# HLS endpoint
location /hls/ {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp/hls;
add_header Cache-Control no-cache;
add_header 'Access-Control-Allow-Origin' $cors_origin always;
# Chunk caching: ts segment lunghi 4s sono cacheable
location ~* \.ts$ {
add_header Cache-Control "public, max-age=60";
}
# M3U8 mai cacheable
location ~* \.m3u8$ {
add_header Cache-Control no-cache;
}
}
# Dash endpoint
location /dash/ {
root /tmp/dash;
add_header Cache-Control no-cache;
}
# Stats endpoint per monitoring
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
}
}
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
# Genera HLS da stream RTMP
hls on;
hls_path /tmp/hls;
hls_fragment 4s;
hls_playlist_length 60s;
# Multi-bitrate con FFmpeg transcoding
exec ffmpeg -i rtmp://localhost/live/$name
-c:v libx264 -b:v 3000k -vf scale=-2:1080 -g 48 -sc_threshold 0
-f flv rtmp://localhost/hls/$name_1080p
-c:v libx264 -b:v 1500k -vf scale=-2:720 -g 48 -sc_threshold 0
-f flv rtmp://localhost/hls/$name_720p
-c:v libx264 -b:v 400k -vf scale=-2:360 -g 48 -sc_threshold 0
-f flv rtmp://localhost/hls/$name_360p;
}
# Applicazione per i diversi bitrate generati
application hls {
live on;
hls on;
hls_path /tmp/hls;
hls_fragment 4s;
hls_nested on;
# Genera master playlist
hls_variant _1080p BANDWIDTH=3000000,RESOLUTION=1920x1080;
hls_variant _720p BANDWIDTH=1500000,RESOLUTION=1280x720;
hls_variant _360p BANDWIDTH=400000,RESOLUTION=640x360;
}
}
}
5. 低速接続の最適化
包括的なビデオ アーキテクチャは、接続が限られている学生でもうまく機能する必要があります。 これらの最適化は、グローバルなスケーラビリティにとって重要です。
// Strategia di preloading intelligente per EdTech
// Precarica la lezione successiva durante la visione di quella corrente
class SmartPreloader {
private readonly HLS_INSTANCE_MAP = new Map<string, Hls>();
private readonly PRELOAD_THRESHOLD = 0.7; // Inizia preload a 70% del video
constructor(private readonly curriculum: string[]) {}
setupPreloading(
currentVideoId: string,
currentHls: Hls,
video: HTMLVideoElement
): void {
const currentIndex = this.curriculum.indexOf(currentVideoId);
const nextVideoId = this.curriculum[currentIndex + 1];
if (!nextVideoId) return;
video.addEventListener('timeupdate', () => {
const progress = video.currentTime / video.duration;
if (progress > this.PRELOAD_THRESHOLD && !this.HLS_INSTANCE_MAP.has(nextVideoId)) {
this.preloadVideo(nextVideoId);
}
});
}
private preloadVideo(videoId: string): void {
// Usa preload: none inizialmente, poi passa a metadata
const preloadHls = new Hls({
startLevel: 0, // Inizia con qualità più bassa
maxBufferLength: 10, // Buffer minimo durante preload
maxMaxBufferLength: 30,
progressive: false,
});
const probeVideo = document.createElement('video');
probeVideo.preload = 'metadata';
preloadHls.loadSource(`/api/video/${videoId}/stream.m3u8`);
preloadHls.attachMedia(probeVideo);
// Precarica solo i primi segmenti
preloadHls.on(Hls.Events.FRAG_LOADED, (_, data) => {
if (data.frag.sn > 3) { // Stop dopo 3 segmenti (~12s)
preloadHls.stopLoad();
}
});
this.HLS_INSTANCE_MAP.set(videoId, preloadHls);
}
getPreloadedHls(videoId: string): Hls | undefined {
return this.HLS_INSTANCE_MAP.get(videoId);
}
}
// Network Quality Detection per adattamento automatico
async function detectNetworkQuality(): Promise<'high' | 'medium' | 'low'> {
// Usa Network Information API dove disponibile
const connection = (navigator as any).connection
|| (navigator as any).mozConnection
|| (navigator as any).webkitConnection;
if (connection) {
const downlink = connection.downlink; // Mbps
if (downlink >= 5) return 'high';
if (downlink >= 1) return 'medium';
return 'low';
}
// Fallback: misura bandwidth con probe request
const startTime = performance.now();
const PROBE_URL = '/api/bandwidth-probe?size=50000'; // 50KB probe
try {
const response = await fetch(PROBE_URL);
const buffer = await response.arrayBuffer();
const duration = (performance.now() - startTime) / 1000;
const sizeMB = buffer.byteLength / 1_000_000;
const speedMbps = (sizeMB * 8) / duration;
if (speedMbps >= 5) return 'high';
if (speedMbps >= 1) return 'medium';
return 'low';
} catch {
return 'medium'; // Default conservativo
}
}
アンチパターン: iOS で Safari の遅延を無視する
iOS の Safari は HLS をネイティブにサポートしていますが、いくつかの重要な動作の違いがあります。
MSE (Media Source Extensions) をサポートしていないため、hls.js は機能しません。常にテストする必要があります
タグ付き <video src="stream.m3u8"> iOS のネイティブ。特徴検出を使用する
と video.canPlayType('application/vnd.apple.mpegurl') hls.jsをインスタンス化する前に。
iPadOS では、iOS 17 バージョン以降、hls.js ライブラリはいくつかの制限付きで動作します。
概要: どのプロトコルを選択するか
| エドテックのシナリオ | 推奨プロトコル | ターゲットレイテンシ |
|---|---|---|
| インタラクティブな 1:1 個別指導 | WebRTC P2P | <300ms |
| 仮想クラス <50 | WebRTC SFU (mediasoup、Janus) | <500ms |
| ライブ配信レッスン | LL-HLS または WebRTC + HLS 下り | 2~5秒 |
| ビデオオンデマンド | マルチビットレート HLS + CDN | 該当なし (VOD) |
| DRM で保護されたコンテンツ | ダッシュ + ワイドバイン/プレイレディ | 該当なし (VOD) |
| 接続が遅い (<1Mbps) | ラダー付き HLS 360p + ABR | 該当なし (VOD) |
結論
教育ビデオに単一の「最適なプロトコル」はありません。常に正しい答えが得られます。 「それはシナリオ次第です。」成熟した EdTech アーキテクチャでは、ライブ インタラクティブ性のために WebRTC を使用します。 スケーラブルな導入のための HLS、および継続性を確保する自動フォールバック システム ネットワーク状態が悪化した場合でも。
ビデオ配信への投資は、コースの完了率に直接関係します。 50,000 人の学生を対象とした 2024 年の調査では、毎秒バッファリングが追加されることが示されました 完了率が 5.8% 低下します。ビデオパイプラインを最適化し、学習を最適化します。
EdTech シリーズの関連記事
- 記事 00: スケーラブルな LMS アーキテクチャ: マルチテナント パターン
- 第07条: CRDTとWebSocketによるリアルタイムコラボレーション
- 記事 08: モバイル ファーストの EdTech: オフライン ファーストのアーキテクチャ







