Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
DataChannel を利用した ホワイトボードアプリ開発事例
Search
hidekuni KAJITA
October 09, 2018
Programming
1k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
DataChannel を利用した ホワイトボードアプリ開発事例
hidekuni KAJITA
October 09, 2018
More Decks by hidekuni KAJITA
See All by hidekuni KAJITA
あれのその後.pdf
hidenba
0
32
NVIDIABroadcast便利
hidenba
0
140
レバーレスアケコンを作った話
hidenba
0
81
「冒険できる社会」を実現する “あしたのチーム”
hidenba
0
160
すぐできるDocker
hidenba
0
1.3k
リズムから生まれるアジャイルな開発
hidenba
0
620
Other Decks in Programming
See All in Programming
Stage 3 Decorators でできること / できないこと / TSKaigi 2026
susisu
1
1.5k
TypeSpec で繋ぐ複数プロダクトの型安全
maroon8021
1
400
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
210
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
560
Copilot CLI の継戦能力を高める コンテキスト管理
nozomutu
1
1.2k
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.5k
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
310
Spec-Driven Development with AI-Agents: From High-Level Requirements to Working Software
antonarhipov
2
450
開発体験を左右するライブラリの API 設計 - GraphQL スキーマ構築ライブラリから考える #tskaigi
izumin5210
2
1.6k
SPMマルチモジュールで テストカバレッジを取得する技法
yosshi4486
0
140
Datadog × OpenTelemetry 入門と実践のあいだ
kn_to_maxpno
1
150
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
500
Featured
See All Featured
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
260
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
370
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
310
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
350
How to Think Like a Performance Engineer
csswizardry
28
2.6k
Making Projects Easy
brettharned
120
6.7k
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
180
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
530
A Soul's Torment
seathinner
6
2.9k
Transcript
DataChannel を利用した ホワイトボードアプリ開発事例 2018/10/9 WebRTC Meetup Tokyo #19 永和システムマネジメント @kunitoo
@hide_nba
2 おしながき • Linkup の概要 • Linkup の機能 • Linkup
の構成 • DataChannel の活用 • 画面共有 • 各クライアントの表示名 • ココさす • ホワイトボード
自己紹介 • 名前: 伊藤邦彦 • 所属: 永和システムマネジメント • 在住: 富山県
• Twitter: @kunitoo • GitHub: @kunitoo 3
4 永和システムマネジメント 管理部 未来企画室 組込み技術 センター 金融システム 事業部 ITサービス 事業部
医療システム 事業部 アジャイル 事業部 事業本部 永和システム マネジメント 【社員数】216名 【事業内容】・情報システム開発および構築 ・パッケージの開発および販売 ・コンピュータ、周辺機器の販売 ・開発技術コンサルティング、教育 【所在地】本社:福井県福井市問屋町3丁目111番地 支社:東京都千代田区神田須田町2丁目3番地1号 沖縄:沖縄県那覇市泊2丁目1-18T&C泊ビル 【在宅勤務制度】: 多くの社員が利用している 【リモートワーク】: 石川、富山に1名ずつ
5 Linkup の概要
6
Linkup • チームのためのリモートコラボレーションツール • 2017年9月頃から事業部の10%ルールとしてスタート • 顔を見ながら画面共有したい • 2018年4月頃から本格始動 •
ホワイトボードに書きながらビデオチャットしたい • 9月20日にオープンβとしてリリース • https://linkup.world/ 7
8 チーム名を決めて試してみて下さい
Linkup のコンセプト • Web に和じゃを作りたい • 執務室内中央のセミオープンな会議スペース • ディスプレイ、ホワイトボードがある •
オフィスのセミオープンな会議スペースに人が 集って議論をすぐスタートできる • 全員が見えるディスプレイ • ホワイトボードが利用できる • 人が集ったことが分かる • リモート(低帯域/低浸透性) から対面(広帯域/高 浸透性)のコミュニケーション 9
Linkup の機能 10
Linkup の機能 • ビデオチャット • 多人数での双方向チャット • ホワイトボード • 図や絵を描いてのイメージ共有
• 画面共有 • ココがさせる • チャット連携 • idobata/slack へのルーム状況の通知 • オープンスペース(実現予定) • ルームに入らずに、ホワイトボード、話している人が分かる • あいづち/うなずき(実現予定) 11
Linkup の利用シーン • チームの朝会 • チャット連携 • チーム内の相談やミーティング • ホワイトボード
• 画面共有 • ここがさせる • ふりかえり • 付箋 12
朝会 • Webhook を設定して おく • メンバー集る • 集っている様子が チャットで分かる
13 Webhook を設定 Idobta のチャット通知例
チーム内の相談やミーティング • チャットで呼びかける • 話題をホワイトボード に書き出す • 画面共有をする 14
チーム内の相談やミーティング • チャットで呼びかける • 話題をホワイトボード に書き出す • 画面共有をする 15
ふりかえり • 手元の付箋に書き溜 めることができる • ホワイトボードに張り つけてチームに共有す る 16
Linkup の構成 17
Linkup の技術要素 • フロントエンド • React • Canvas(fabric.js) • SkyWay
• CloudFront • バックエンド(API) • Ruby on Rails • GraphQL • ECS(Fargate) • インフラ • Terraform 18
DataChannel の活用 19
DataChannel の活用 • 画面共有 • 各クライアントの表示名 • ココさす • ホワイトボード
20
画面共有での利用 1. skyway-screenshare を利用してstreamオブジェクトを取得 2. 画面共有を始めたことを Data Channel で通知 3.
通知を受けた側から接続を開始する `peer.call` 4. 画面共有側で stream を設定して、接続を完了する 21
クライアント情報の共有 1. stream を受信したとき名前を送り、相手の名前を要求 する(REQUEST_DISPLAY_NAME) 2. 名前の要求が来たら、 a. 要求相手の名前を peerId
を元にカメラ stream と 紐付ける b. 自分の名前を送信する(DISPLAY_NAME) 22
ココさす • 画面共有の領域にCanvasを用 意、クリックした位置を取得 • video の表示しているサイズから 相対位置を割り出し送信する (HTMLVideoElement.videoHeight ,
videoWidth) • 受信側で相手のvideoサイズと自 分のvideoサイズから位置を割り 出しアニメーションを描画する 23
demo 24
交代 25
自己紹介 • 名前: かじたひでくに • 所属: 永和システムマネジメント • Twitter: @hide_nba
• GitHub: @hidenba 26
ホワイトボードで出来ること • 手書き 27
ホワイトボードで出来ること • 付箋 28
ホワイトボードで出来ること • 画像のアップロード/ダウンロード 29
demo 30
SkyWayでのデータ送信 const peer = new Peer({ key: SKYWAY_KEY, debug: LOG_LEVEL
}) const room = peer.joinRoom(roomName, { mode: 'sfu', stream: stream }) room.send({data: 'send data'}) 31
SkyWayでのデータ受信 const peer = new Peer({ key: SKYWAY_KEY, debug: LOG_LEVEL
}) const room = peer.joinRoom(roomName, { mode: 'sfu', stream: stream }) room.on('data', (data) => { console.log(data) }) 32
送受信しているデータの構造 { action: 'UPDATE_WHITEBOARD', data1: dataobject, data2: dataobject, data3: dataobject
} 33
受信時のイベントハンドリング room.on('data', dataObject => { if (dataObject.data.action === 'START_SCREEN_SHARE') {
} else if (dataObject.data.action === 'STOP_SCREEN_SHARE') { } else if (dataObject.data.action === 'REQUEST_DISPLAY_NAME') { } else if (dataObject.data.action === 'UPDATE_WHITEBOARD') { const { id, payload, operation, uuid } = dataObject.data update(id, operation, uuid, payload) ... }) 34
オブジェクトの追加 1. Objectの追加 2. Objectをシリアライズして JSONにする 3. JSONをサーバへ送信 4. Roomに接続しているPeer
と同期 35 send add on(data) 1. データ受信 2. JSONからObjectへ 3. Canvasに描画
追加操作の送信 const objectPayload = JSON.stringify(fabricObject) request(`${LINKUP_API_URL}/graphql`, query) room.send({ action: 'UPDATE_WHITEBOARD',
payload: objectPayload, operation: 'add', uuid: uuid }) 36
ホワイトボード更新イベント受信 room.on('data', dataObject => { if (dataObject.data.action === 'START_SCREEN_SHARE') {
} else if (dataObject.data.action === 'STOP_SCREEN_SHARE') { } else if (dataObject.data.action === 'REQUEST_DISPLAY_NAME') { } else if (dataObject.data.action === 'UPDATE_WHITEBOARD') { const { id, payload, operation, uuid } = dataObject.data update(id, operation, uuid, payload) }) 37
ホワイトボード更新 switch (operation) { case 'add': this.addObjectFromJson(data) break case 'remove':
this.removeActionByUUID(prevOperation.uuid) break ... } 38
オブジェクトの移動 1. Objectの移動 2. Objectの削除情報を各クライア ントと同期 3. 削除情報をサーバへ送信 4. 移動後のObjectをシリアライズし
てJSONにする 5. JSONをサーバへ送信 6. 各Peerと同期 39 send remove on(data) 1. 削除イベント受信 2. UUIDの一致するObjectを削除 3. 移動後の追加データ受信 4. JSONからObjectへ 5. Canvasに描画 send add on(data)
削除操作の送信 const objectPayload = JSON.stringify(fabricObject) request(`${LINKUP_API_URL}/graphql`, query) room.send({ action: 'UPDATE_WHITEBOARD',
operation: 'remove', uuid: uuid }) 40
ホワイトボード更新イベント受信 room.on('data', dataObject => { if (dataObject.data.action === 'START_SCREEN_SHARE') {
} else if (dataObject.data.action === 'STOP_SCREEN_SHARE') { } else if (dataObject.data.action === 'REQUEST_DISPLAY_NAME') { } else if (dataObject.data.action === 'UPDATE_WHITEBOARD') { const { id, payload, operation, uuid } = dataObject.data update(id, operation, uuid, payload) }) 41
ホワイトボード更新 switch (operation) { case 'add': this.addObjectFromJson(data) break case 'remove':
this.removeActionByUUID(uuid) break ... } 42
追加操作の送信 const objectPayload = JSON.stringify(fabricObject) request(`${LINKUP_API_URL}/graphql`, query) room.send({ action: 'UPDATE_WHITEBOARD',
payload: objectPayload, operation: 'add', uuid: uuid }) 43
ホワイトボード更新イベント受信 room.on('data', dataObject => { if (dataObject.data.action === 'START_SCREEN_SHARE') {
} else if (dataObject.data.action === 'STOP_SCREEN_SHARE') { } else if (dataObject.data.action === 'REQUEST_DISPLAY_NAME') { } else if (dataObject.data.action === 'UPDATE_WHITEBOARD') { const { id, payload, operation, uuid } = dataObject.data update(id, operation, uuid, payload) }) 44
ホワイトボード更新 switch (operation) { case 'add': this.addObjectFromJson(data) break case 'remove':
this.removeActionByUUID(prevOperation.uuid) break ... } 45
46 チーム名を決めて試してみて下さい