はじめに
動画や大きなzipファイルを配信するたびに、CDNの転送量課金やサーバーの帯域が気になったことはないでしょうか。アクセスが増えるほど費用がかさむのは、配信元のサーバーが1台(または少数)に集中しているからです。
WebTorrent は、この構図をひっくり返すJavaScript製のBitTorrentクライアントです。Node.jsだけでなくブラウザの中でもそのまま動作し、ファイルをダウンロードした人がそのまま次の配信元(シード)にもなります。プラグイン不要、ブラウザ標準のWebRTCだけでピア同士が直接データをやり取りするので、閲覧者が増えるほど配信の負荷が分散されていくのが特徴です。
とはいえ、読むより触った方が早いと思います。ファイルを選ぶだけでWebTorrentが実際にTorrent化・ハッシュ計算・プレビュー表示までこなす様子を、通信に依存せず確認できるサンプルを用意しました。先に挙動を見たい方はこちらからどうぞ。
WebTorrentとは
WebTorrentは、npmパッケージwebtorrentとして配布されているストリーミング対応のBitTorrentクライアントです。Node.js向けとブラウザ向けで同じAPI・同じコードベースを使えるのが最大の特徴で、CLIツールとしても、Webページに組み込むライブラリとしても利用できます。
ブラウザ環境では通常のBitTorrent通信(TCP/UDP)の代わりに**WebRTC(データチャネル)**を使ってピア同士が接続します。そのためプラグインのインストールは不要ですが、通信できる相手はWebRTC対応のWebTorrentクライアント同士に限られます。
主な特徴
- Node.js/ブラウザの両対応 - 同じ
webtorrentパッケージがサーバーサイドCLIとフロントエンドの両方で動く - プラグイン不要のP2P通信 - ブラウザではWebRTCのデータチャネルでピア間通信を実現
- ストリーミング再生 - 動画などのファイルをダウンロードしながら順次再生できる
- マグネットURI/DHT/トラッカー対応 - 標準的なBitTorrentのピア発見手段をひととおりサポート
- 純粋なJavaScript実装 - ネイティブ依存なしで動作する
インストール
npm install webtorrent
CLIとして使いたい場合は、コマンドラインツールを提供するwebtorrent-cliを利用します。
npm install webtorrent-cli -g
WebTorrentのサンプルを動かす
WebTorrentの本領はインターネット上の見知らぬピアとのダウンロードにありますが、それを記事内でそのまま動かすと、たまたま相手が見つからず何も起きない、という不安定なデモになってしまいます。そこで下のサンプルでは、client.seed()にファイルを渡してその場で**Torrent化(ハッシュ計算)**する、通信を一切発生させない使い方を紹介します。tracker: false・dht: false・lsd: falseを指定してピア探索を止めているので、オフラインでも同じ結果になります。
import WebTorrent from 'webtorrent'
const client = new WebTorrent({ tracker: false, dht: false, lsd: false })
client.seed(fileList, { announce: [] }, (torrent) => {
console.log(torrent.infoHash) // トレントを一意に識別するハッシュ値
console.log(torrent.magnetURI) // 共有用のマグネットURI
console.log(torrent.files[0].name)
})
実際に手元のファイルを選んで、torrent.infoHashやtorrent.magnetURIが計算される様子を確認してみてください。
ファイルを変えるたびにinfoHashが変わるのが確認できるはずです。これはファイルの中身から一意に計算される値で、同じファイルなら誰の環境で計算しても同じinfoHashになります。これがBitTorrentネットワーク上でそのファイルを識別するIDそのものです。
基本的な使い方
実際のダウンロードではclient.add()にマグネットURIを渡します。トラッカーやDHT経由で見つかったピアとWebRTCで接続し、ピースが揃ったファイルから順にダウンロードが進みます。
import WebTorrent from 'webtorrent'
const client = new WebTorrent()
const magnetURI = 'magnet:?xt=urn:btih:...'
client.add(magnetURI, async (torrent) => {
console.log('info hash:', torrent.infoHash)
const file = torrent.files.find(f => f.name.endsWith('.mp4'))
const blob = await file.blob()
document.querySelector('video').src = URL.createObjectURL(blob)
})
進捗を追いたい場合は、torrentオブジェクトのイベントとプロパティを使います。
torrent.on('download', (bytes) => {
console.log('progress', Math.round(torrent.progress * 100) + '%')
console.log('peers', torrent.numPeers)
console.log('speed', torrent.downloadSpeed, 'bytes/sec')
})
torrent.on('done', () => {
console.log('ダウンロード完了')
})
このclient.add()は実在のBitTorrentトラッカーやピアへの接続が前提のため、この記事のサンプルとしては埋め込まず、コード例として紹介するにとどめます(読者の回線・時間帯によって結果が変わってしまうためです)。
実践的なユースケース
WebTorrentで共有用マグネットURIを発行する
ファイルをサーバーにアップロードせず、ブラウザ上で選んだ瞬間に共有用のURIを作りたい場面があります。client.seed()が返すtorrentオブジェクトのmagnetURIプロパティを使うと、そのファイルをダウンロードするためのマグネットリンクをその場で発行できます。
client.seed(files, { announce: [] }, (torrent) => {
console.log(torrent.magnetURI)
// magnet:?xt=urn:btih:xxxxxxxx...&dn=example.png
})
下のサンプルはファイル選択後にマグネットURIを表示し、ボタンでクリップボードにコピーできるようにしたものです。
本番でトラッカーを有効にすれば(tracker: falseを外すだけです)、このマグネットURIを受け取った別のWebTorrentクライアントが実際にダウンロードを開始できます。
選んだファイルをその場でプレビュー表示する
WebTorrentのfileオブジェクトはawait file.blob()でファイルの中身をW3CのBlobとして取り出せます。ダウンロードしたファイルはもちろん、シード中の(=自分が元から持っている)ファイルでも同じAPIで扱えるので、URL.createObjectURL()と組み合わせれば画像や動画をそのままプレビュー表示できます。
const file = torrent.files[0]
const blob = await file.blob()
const url = URL.createObjectURL(blob)
// file.type (mimeタイプ) に応じて <img>/<video>/<audio> に振り分ける
画像・動画・音声ファイルを選ぶと、種類に応じて表示を切り替えるサンプルです。
file.typeはファイル拡張子から推測されたMIMEタイプです。ここをfile.nameやfile.lengthに差し替えれば、プレビューの代わりにファイル一覧UIを作ることもできます。
まとめ
WebTorrentは、BitTorrentプロトコルをNode.jsとブラウザの両方で同じAPIとして扱えるライブラリです。ブラウザではWebRTCを使うことでプラグインなしにピア同士が直接データをやり取りでき、閲覧者が増えるほど配信の負荷が分散されるという、通常のサーバー配信とは逆の性質を持っています。
client.add()によるダウンロードは実際のトラッカー・ピアが前提になりますが、client.seed()によるTorrent化やマグネットURIの発行、file.blob()によるファイルプレビューは、通信に依存せずブラウザだけで完結します。まずはこのあたりから触ってみて、実際のファイル共有機能を組み込む際はclient.add()まわりのイベントを追ってみてください。