0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zoom Video SDK Web に Code with AI が同梱→有無で実装を比べてみた

0
Posted at

Gemini_Generated_Image_8s41l78s41l78s41.jpeg

何が配られたのか

Zoom Video SDK に、AI コーディングツール向けのドキュメントが同梱されるようになりました。

  • Video SDK for Web — バージョン 2.5.0 から。npm パッケージに agent skill と JSON 型定義が入り、CLI でインストールする
  • ネイティブ SDK(Android / iOS / Linux / macOS / Windows) — バージョン 2.5.7 から。パッケージ内の Docs/ フォルダに同梱される

公式ドキュメントは Using AI assistants with the Video SDK にあります。

この記事では Web 版を扱います。導入はコマンド一発です。

npm install @zoom/videosdk
npx @zoom/videosdk install-skill --target claude   # cursor | codex | all

実行するとこうなります。

Installed Video SDK skill (claude) → /path/to/project/.claude/skills/video-sdk-web
Done. Restart Cursor / Claude Code / Codex if it is already open.

展開されたのは 46 ファイル。SKILL.md 本体、references/ が 45 本(認証、セッション、イベント、音声、映像、画面共有、チャット、録画、フレームワーク統合、ブラウザ対応、エラーコード)、troubleshooting/ が 1 本です。( 2.5.0 時点)

--global を付ければホームディレクトリに入れて全プロジェクトで使えます。

さて、これを入れると実際どう変わるのか。同じプロンプトを投げて比べてみました。

検証条件

何と何を比べるか

SDK 同梱物
選手A 2.4.7 .d.ts 型定義のみ。skill 同梱なし
選手B 2.5.0 agent skill + JSON 型定義 + .d.ts

2.4.7 には skill/ も bin/(CLI 本体)も dist/types-json/ もありません。dist/types/*.d.ts だけがあります。

バージョンが違うから比較できなくない?

型定義の差分を実測しました。

ファイル 2.4.7 2.5.0 差分行数
videoclient.d.ts 1304 行 1304 行 0
media.d.ts 3157 行 3176 行 19
event-callback.d.ts 2459 行 2462 行 17

attachVideo / detachVideo / attachShareView などのコア API は 2.4.7 の型定義にも全て存在します。API 面はほぼ同一です。

Web 検索は両選手ともオン

片方だけ切ると変数が 2 つ動いてしまうため、両方オンにしました。

そもそも skill の references/ は、各ファイルの先頭に

<!-- Source: https://developers.zoom.us/docs/video-sdk/web/... -->
<!-- Fetched: 2026-06-15 -->

というヘッダーが付いた公式ドキュメントのスナップショットです。情報そのものは Web にもある。だから差が出るとすれば、それは「情報の有無」ではなく「必ず読ませる構造」の効果だと言えます。

環境の隔離でハマった話

ここが地味に一番時間を食いました。

Claude Code はセッションを跨いで知識を運ぶ仕組みを持っています。CLAUDE.md と auto memory です。これが残っていると「先攻が学んだ API 名を後攻が盗み見」かねません。

そこで HOME を差し替えて起動しました。

HOME=/tmp/clean-home claude

ところが /memory を叩くと、まだ /Users/xxx/.claude/CLAUDE.md が読み込まれている。

原因は HOME ではなく cwd でした。Claude Code は現在ディレクトリから親を遡って CLAUDE.md を集めます。プロジェクトを ~/SDK/VideoSDK/Web に置いていたので、/Users/xxx まで登って拾っていたわけです。

プロジェクトを /tmp 配下に移したら消えました。

mkdir -p /tmp/vsdk-lab/01247 && cd /tmp/vsdk-lab/01247
HOME=/tmp/clean-home claude

/memory が空になったことを確認してから実験開始。選手A と選手B で HOME も別々にしています。

ただの余談ですが、Claude Code のドキュメントには「CLAUDE.md は文脈であって強制設定ではない(treats them as context, not enforced configuration)」と明記されています。つまりプロンプトで「メモリを参照しないで」と指示しても意味がありません。物理的に隔離するしかないです。

投げたプロンプト

両選手に一字一句同じものを投げました。取り急ぎミニマムの比較ということでシンプルにアプリ部のみ。認証なし。しかもビデオと音声のみ。画面共有すらなし。

Zoom Video SDK for Web を使って、最小構成のビデオ会議 Web アプリを実装してください。

## 環境
- TypeScript + Vite、フレームワークなしの Vanilla DOM
- パッケージ: @zoom/videosdk (node_modules に導入済み)
- 署名済みの Video SDK JWT、セッション名、表示名は .env から読み込む
  (JWT の生成処理は実装対象外)

## 機能要件
1. セッションへの参加と退出
2. マイクの開始とミュート切り替え
3. カメラの開始と停止
4. 自分と他の参加者、全員の映像を画面に表示する
5. 参加順に関わらず、セッションにいる全員の映像が表示されること
   (後から参加した場合も、先にいた参加者の映像が見えること)
6. 参加者名の一覧表示
7. 現在の接続状態を UI に表示すること
   切断中・再接続中・セッション終了をユーザーが判別できること
8. 退出時にイベントリスナーと DOM を後片付けすること

## 実装条件
- UI テキストとコード内コメントは英語
- ボタン連打による二重実行を防ぐこと
- エラーは原因が分かる形でユーザーに提示すること
- Chrome 2 タブで同一セッションに参加して動作確認できる状態にすること

## 出力
実装ファイル一式と起動手順。
最後に、使用した API・イベント名・DOM 要素について、
公式に存在することを確認できたものと、確認できていないものを区別して報告してください。

API 名・イベント名・DOM 要素名は一切与えていません。 要件5と要件7は、機能要件としては自然な日本語ですが、満たすには特定の API とイベントを知っている必要があります。

「推測で書くな」とも書いていません。これも skill に記述済みだったので前提としては与えず。最後の「確認できたもの / できていないものを区別して報告」が答案用紙になります。

skill には次の一文があります。

Never invent SDK API surface. Do not guess, recall, or "reconstruct" method names, event strings, enum values... This rule overrides convenience and speed.

これが効いているかどうかは、コードだけでなく報告の書き方にも出てきそうです。

結果:面白かった違い

1. レンダリング先の設計が根本的に違った

これが最大の差でした。

選手A は参加者ごとに「タイル」 <div class="tile"> を作り、その中に「コンテナ」 <video-player-container> を動的生成します。参加者が増えるたびに container が増えます。

選手B は index.html に container を 1 つだけ静的配置し、そこへ <video-player> を追加していきます。

<!--
  Single, stable video-player-container reused for the whole session.
  The SDK owns its rendering surface, so we never destroy/recreate it.
-->
<video-player-container id="video-grid" class="video-grid"></video-player-container>

コメントで理由まで書いています。skill にはこう書かれています。

In React/Vue, do not place it behind conditional rendering that destroys it — keep it mounted and toggle visibility with CSS

video-player-container を破棄・再生成すると、ブラウザのレンダリングサーフェス上限を食い潰して映像が出なくなる、という話です。skill の記述が設計判断としてそのまま転写されているのがわかります。

実際、参加者が 2 人 → 4 人 → 2 人と変動しても container は作り直されず、<video-player> が足し引きされるだけでした。CSS の flex: 0 0 calc(50% - 4px) が効いて 2 列 → 2×2 → 2 列と綺麗に並びます。(画像は加工しております)

Gemini_Generated_Image_e49eywe49eywe49e.jpeg

選手A の方式では、ここで container の生成・破棄が走り回っていました。

2. 報告の質が段違いだった

選手A の自己申告は、ファイル冒頭のコメント 1 行だけでした。

All identifiers used against the SDK (methods, events, payload fields) were taken from the bundled type definitions in node_modules/@zoom/videosdk/dist/types. (SDKで使用されるすべての識別子(メソッド、イベント、ペイロードフィールド)は、node_modules/@zoom/videosdk/dist/typesにバンドルされている型定義から取得されました)

対して選手B は、シンボルごとに行番号付きの検証表を出してきました。

Symbol Source file
ZoomVideo.createClient() zoomvideo.d.ts:18
client.join(topic, token, userName, sessionPassword?) videoclient.d.ts:1151
stream.attachVideo(userId, VideoQuality, element?) media.d.ts:1850
VideoQuality.Video_360P (=2) common.d.ts:361

さらに面白いのがこの 2 行です。

Corrected during implementation (initially guessed, then fixed)

  • stream.getAudioTrack() — does not exist in v2.5.0. Replaced with the verified getCurrentUserInfo().audio === 'computer' check.
  • Participant as a named import — it is not exported from the package.

「一度推測で実装しようとして、型定義で照合して間違いに気づき、直した」というプロセスを自己開示している。

getAudioTrack() は存在しないメソッドです。代わりに使ったのは Participant の audio フィールド('' | 'computer' | 'phone' の 3 値)で、'computer' ならコンピュータオーディオで参加済み、という判定です。

「推測するな」という指示が、コードだけでなく出力の書き方まで変えたわけです。

3. .env に無い変数を、型定義から補完してきた

これは偶然見つかった話です。

選手A のとき .env.example がアクセス権ルール( .env や .env.example は読み込ませない、というポリシールール)の都合で読めていませんでした。なので当然ながら必須となるトピック、ユーザー名、そしてトークンの3つだけを .env から読み込む実装となっていました。そこで選手B では env.example.txt にリネームして確実に読ませたのですが、パスコードの変数は条件を同じくするために、あえて入れませんでした。

にもかかわらず、選手B のコードにはこう書かれていました。

const PASSCODE = import.meta.env.VITE_ZOOM_SESSION_PASSCODE ?? "";
// ...
await client.join(TOPIC, TOKEN, USER_NAME, PASSCODE);

.env に無いものを補完しています。参照元は client.join(topic, token, userName, sessionPassword?) という型定義のシグネチャしかありません。空文字フォールバックまで入れて、あれば渡す・なければ渡らない、という作りになっています。えらい。とても偉い。

4. 二重クリック防止が「問題ごと消えた」

選手A も二重クリック防止は実装していました。guard() という関数でボタンを disabled にする方式です。ただし復元ロジックにバグがあり、join() が意図的に設定した disabled を finally が上書きしてしまい、セッション中なのに Join ボタンが押せる状態になっていました。片手オチ感がありますね。

選手B は違うアプローチを取りました。

function syncButtons(): void {
  els.btnJoin.disabled = inSession || busy.join;
  els.btnLeave.disabled = !inSession || busy.leave;
  // ...
}

inSession と busy.* から毎回状態を導出する設計です。ボタンの状態を個別に書き換える箇所が存在しないので、上書きバグが起きようがない。「二重クリック対策」というより「状態管理の設計」で解決しています。

5. ダークテーマへのこだわり(趣味?)

選手B の CSS にこんな箇所があります。

/*
 * The SDK renders video onto <video-player> children of this container.
 * Keep it transparent (no opaque background) so rendered video is visible.
 */
video-player-container.video-grid {
  background: #0d0f12; /* outer letterbox only; players sit on top */
}

「透明に保て」と自分でコメントに書いた直後に不透明色を敷いています。

skill には「container / player に不透明な背景を敷くと映像が塗り潰される」という警告があり、それを認識した上で「video-player は container の直接の子だから必ず手前に来る。だから letterbox としてなら問題ない」と判断して逸脱している。実際、映像はきちんと表示されました。

盲目的に従うのでも無視するのでもなく、根拠を書いてから逸脱する。地味に感心したポイントです。今回 Claude Code を使ったんですが、Claude さんの趣味ということにしておきます。

採点表

skill 自身が持っている "Common implementation checks" をベースに評価軸を作り、実験前に固定しました。

# 項目 選手A 選手B
1 init() → join() の順序 ○ ○
2 join 後に getMediaStream() ○ ○
3 attachVideo() の戻り値を container に append ○ ○
4 video-player に CSS で寸法 ○ ○
5 不透明 background を敷いていない × ○(上記の通り)
6 peer-video-state-change をバインド ○ ○
7 join 後 getAllUser() で既存参加者の映像を復元 ○ ○
8 connection-change の 3 状態を処理 ○ ○ + errorCode も表示
9 active-media-failed をバインド × ○
10 二重クリック防止 △(バグあり) ○
11 leave 後の destroyClient() × ×
12 patchJsMedia ○ ○
13 checkSystemRequirements() × ○
14 sessionPasscode を join に渡す × ○

選手A も SDK API の使い方自体はかなり正しく書けています。attachVideo の使い方も、getAllUser() による復元も通っている。差が出たのは「知らないと入れられないもの」でした。

#9 の active-media-failed がわかりやすい例です。選手A は auto-play-audio-failed を拾っていました。方向性は合っているけれど別です。「近いイベントは知っている」のが選手A、「イベント一覧を型定義と照合して並べた」のが選手B。

#13 の checkSystemRequirements() も同じ構図です。プロンプトの要件に書いていない項目ですが、選手B だけが入れてきました。

今回 skill があっても直らなかったもの

一つ注目したのは、選手Bの唯一のバツ。

destroyClient() を両選手とも落としました。 セッション終了後に init() を呼び直しており、コンソールに PubSub already loaded, using existing version が出ます。

面白いのは、skill にはちゃんとガイダンスがあったことです。実験後に検索したところ、references/sessions.md の "Clean up the Video SDK" セクションと、React / Vue / Svelte / Angular / Nuxt の各フレームワーク統合ドキュメントに記述がありました。選手B 本人にも聞いてみました。

(ぼく) 一つ質問なんだけど、セッション終了後に再度 SDK を init する場合、先に desroyClient() するといったガイダンスは skill には存在しなかった?直さなくていいから教えて。
(選手B ) はい、存在します。 references/sessions.md("Clean up the Video SDK" セクション)に、セッション終了後は映像のレンダリングを止め destroyClient() を呼んで SDK を破棄し、再利用できるようにするというガイダンスがあります。
今回の実装は destroyClient() を呼んでおらず leave() + 自前の teardown のみなので、この点はスキルのガイダンスを取りこぼしています。修正しましょうか?

つまり skill を入れても、そこに書いてあることが全部反映されるわけではない。読ませる構造があっても取りこぼしは起きます。

もう 1 つ。選手B には [hidden] 属性に対応する CSS 定義が無く、プレースホルダが消えずに映像を覆い隠す、という不具合がありました。これは素の CSS の話で、Video SDK とは無関係だったんですが、考えてるうちに自分の書いたものを忘れちゃったようです。

まとめ

やってみた実感として、skill の効果は 3 つに整理できます。

1. 手直しが減る。 選手A は動かすまでに CSS の修正が必要でした。選手B はそのまま動きました。加えて選手A のコードには、二重クリック防止のバグ、sessionPasscode の未使用など、レビューで指摘すべき点が複数残っていました。

2. 「知らないと入れられないもの」が入る。 active-media-failed、checkSystemRequirements()、isInFailover による再接続表示。要件に書いていない・書きようがない項目が、skill 側から供給されます。

3. 設計が変わる。 これが一番大きい。video-player-container を使い回す設計は、後から気づいて直すのが面倒な種類の判断です。最初から正しく始められる価値は大きい。

一方で万能ではありません。destroyClient() のように、skill に書いてあっても落ちるものはあります。レビューは要ります。 ただしレビューの中身が「API 名が実在するか」から「設計判断が妥当か」に上がる。これは大きな違いだと思います。

Web 版は

npx @zoom/videosdk install-skill

の一行です。SDK をアップグレードしたら skill も入れ直すのを忘れずに。


検証環境

  • Claude Code v2.1.172 / Opus 4.8
  • @zoom/videosdk 2.4.7 / 2.5.0
  • Vanilla TypeScript + Vite、Chrome 2 タブで動作確認
  • 試行回数 n=1。LLM の出力は毎回変わるため、この記事の差分がそのまま再現される保証はありません

今回の skill の scope は Video SDK for Web のみです。埋め込み型の Zoom Meetings / Webinars は Meeting SDK の管轄なので、この skill では扱えません。ですが、Zoom 公式の Github には全ての Build Platform 製品を含む開発者向けの skill 群が網羅されています!こちらもぜひ活用してみてください

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?