フロントエンド 2026.06.26

QRコードでスタジアムが光る仕組みを実装して検証した

約15分で読めます

「ハッキング」「爆発する」といったデマが飛び交うスタジアムのスマホ一斉点灯演出。getUserMediaのtorch制約とDate.now同期で実際に再現し、その仕組みを技術的に解説します。

こんな疑問を持ったことはありませんか?

コンサート会場やスポーツスタジアムで、観客が一斉にスマホのライトを点滅させる演出を見たことがあるはずです。「QRコードを読み込むだけで、見知らぬ他人のスマホを操作できるなんて怖い」「あれはハッキングじゃないか」「スマホが爆発するって聞いた」——SNSでこういった声を見かけることは、決して珍しくありません。

技術的な背景を知らないと、確かに不気味に感じるかもしれません。しかし実態はまったく異なります。この記事では、WebブラウザのAPIだけで同様の仕組みを実際に実装・検証した結果を、コードを交えながら正確にお伝えします。デマが広がる前に、仕組みを理解しておくことは、Web担当者やエンジニアにとって重要な知識です。

動作デモは /demo/torch-stadium で公開していますので、対応ブラウザ(AndroidのChrome等)でぜひ確認してください。

実際にSNSで拡散された元の動画がこちらです。アルジェリア代表 対 ヨルダン代表の試合で、スタジアムのスクリーンに表示されたQRコードを読み込むと、観客のスマホのライトが一斉に同期して動く光の演出になっています。


なぜ「ハッキング」ではないのか——仕組みの全体像

まず前提として整理しておきます。スタジアム演出で使われている仕組みは、大きく2つの技術で成り立っています。

  1. getUserMedia + MediaStreamTrackのtorch制約 — ブラウザからカメラ・フラッシュライトへのアクセス
  2. Date.nowベースの時刻同期 — 複数端末のライト点滅タイミングを揃える仕組み

重要なのは、ユーザーが自分でQRコードを読み込み、表示されたWebページを開いた瞬間に、ブラウザの権限ダイアログが表示されるという点です。ユーザーが「許可」を押さない限り、カメラにもライトにもアクセスできません。これはiOSのSafari、AndroidのChromeいずれも同様の動作をします。「勝手に操作される」ことは、仕様上あり得ないのです。

また「爆発する」という噂については、LEDフラッシュは電力消費が極めて小さく、連続点灯でも発熱の問題は生じません。この種のデマは、技術の不透明さから生まれる典型的なパターンです。

flowchart TD
    A[QRコードをスキャン] --> B[WebページをChromeで開く]
    B --> C{カメラ権限を許可する?}
    C -->|ユーザーが許可| D[getUserMedia でカメラストリーム取得]
    C -->|拒否| E[何も起きない]
    D --> F[MediaStreamTrack.applyConstraints torch:true]
    F --> G[バックカメラのフラッシュLEDが点灯]
    G --> H[Date.now 基準で点滅タイミングを同期]
    H --> I[全端末が同じパターンで点滅]

フロントエンド開発をお探しですか?

React・Vue・モダンなUI/UX開発をサポートします

無料で相談する

実装の核心部分:getUserMediaとtorch制約

カメラストリームの取得とtorchの有効化

Webブラウザからスマホの背面フラッシュライトを制御するには、getUserMediaでカメラストリームを取得したうえで、MediaStreamTracktorch制約を適用します。以下が最小構成のコードです。

async function enableTorch() {
  try {
    // 背面カメラを指定してストリームを取得
    const stream = await navigator.mediaDevices.getUserMedia({
      video: {
        facingMode: { exact: 'environment' },
        width: { ideal: 1280 }
      }
    });

    const track = stream.getVideoTracks()[0];

    // torch対応確認
    const capabilities = track.getCapabilities();
    if (!capabilities.torch) {
      console.warn('このデバイスはtorchに対応していません');
      return null;
    }

    // フラッシュLEDを点灯
    await track.applyConstraints({ advanced: [{ torch: true }] });
    return track;
  } catch (err) {
    console.error('torch有効化エラー:', err);
    return null;
  }
}

async function disableTorch(track) {
  if (track) {
    await track.applyConstraints({ advanced: [{ torch: false }] });
  }
}

getCapabilities()torchプロパティの存在を確認してからapplyConstraintsを呼ぶのが基本パターンです。この確認を省くと、非対応端末でエラーが投げられます。

Date.nowによる複数端末の時刻同期

演出の核心は「タイミングを揃える」ことです。サーバーとのWebSocket接続が使える場合もありますが、最もシンプルな方法はクライアント側のDate.nowをアンカーとした計算です。

// サーバーから受け取った演出スケジュール(例)
const schedule = {
  startAt: 1720000000000, // UTCミリ秒
  pattern: [
    { on: 0,    off: 300  },  // 0ms点灯 → 300msで消灯
    { on: 500,  off: 800  },
    { on: 1000, off: 1500 },
  ],
  loopDuration: 2000  // 2秒でループ
};

function runSchedule(track, schedule) {
  function tick() {
    const now = Date.now();
    const elapsed = (now - schedule.startAt) % schedule.loopDuration;

    // 現在の経過時間がどのパターンに該当するか判定
    let shouldBeOn = false;
    for (const step of schedule.pattern) {
      if (elapsed >= step.on && elapsed < step.off) {
        shouldBeOn = true;
        break;
      }
    }

    // 状態変化があるときだけapplyConstraintsを呼ぶ
    if (shouldBeOn !== currentState) {
      currentState = shouldBeOn;
      track.applyConstraints({ advanced: [{ torch: shouldBeOn }] });
    }

    requestAnimationFrame(tick);
  }

  let currentState = false;
  tick();
}

startAt(演出開始のUnixタイムスタンプ)をQRコードのURLパラメータやWebSocket経由で全端末に配布すれば、追加のサーバー通信なしに同期が成立します。各スマホは自身の時計でDate.now()を呼ぶだけで、同じelapsed値を持つからです。

// QRコードのURL例:
// https://example.com/torch?startAt=1720000000000

const params = new URLSearchParams(location.search);
const startAt = parseInt(params.get('startAt'), 10);

// startAtが未来の時刻なら待機、過去なら即開始
const waitMs = Math.max(0, startAt - Date.now());
setTimeout(() => runSchedule(track, { startAt, ...pattern }), waitMs);

よくある実装ミスと対処法

❶ iOSでtorchが動作しない

執筆時点でiOSのSafariはtorch制約に対応していませんgetCapabilities()の返却値にtorchプロパティが含まれないため、何も起きずに終わります。これを知らずに「iOSで動かない、実装が間違っている」と半日悩むケースがあります。

対処は明確で、capabilities.torchの存在確認を徹底し、非対応の場合は画面の明度をCSSで最大にして代替演出に切り替える方法が現実的です。

if (!capabilities.torch) {
  // 画面輝度を最大にして代替
  document.body.style.backgroundColor = '#ffffff';
  document.body.style.filter = 'brightness(1)';
  // …点滅ロジックは同じスケジューラで動かす
}

❷ applyConstraintsを毎フレーム呼んでしまう

requestAnimationFrame内で状態変化の有無を確認せず、毎フレームapplyConstraintsを呼ぶと、不要なI/Oが大量発生してフレームレートが落ちます。前述のコード例のようにcurrentStateフラグで変化があった時だけ呼ぶことが重要です。

❸ ストリームの解放漏れ

ページ離脱時や演出終了時にstream.getTracks().forEach(t => t.stop())を忘れると、カメラが開放されず他のアプリがカメラを使えなくなります。visibilitychangeイベントやbeforeunloadでクリーンアップを確実に行ってください。

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    stream.getTracks().forEach(t => t.stop());
  }
});

❹ HTTPSでないと権限ダイアログが出ない

getUserMediaはセキュアコンテキスト(HTTPS または localhost)でのみ動作します。HTTP環境でテストしていて「権限を求めてこない」というケースは、ほぼこれが原因です。


フロントエンド開発をお探しですか?

React・Vue・モダンなUI/UX開発をサポートします

無料で相談する

Web制作・UI改善もお任せください

フロントエンド開発

モダンな技術で、使いやすく美しいWebサイトを実現します

200件以上の制作実績 顧客満足度97% 初回相談無料

※ 通常1営業日以内にご返信します

まとめと次のステップ

スタジアムのスマホ一斉点灯演出は、getUserMedia + torch制約 + Date.now同期という、いずれも公開されたWeb標準APIの組み合わせで成立しています。「ハッキング」でも「乗っ取り」でもなく、ユーザーが自発的に許可した権限の範囲内で動作するWebアプリケーションです。

この仕組みは、イベント演出にとどまらず、工場内の作業指示システム、防災訓練のアラート演出、店頭プロモーションなど、複数端末を時刻同期させて一斉制御するユースケース全般に応用できます。

実装を試す際は、まずデモページ /demo/torch-stadium でAndroid Chromeの挙動を確認し、その後ローカル環境(localhost)でコードを動かしてみることをお勧めします。

自社サービスやイベントへの応用について「どこまで自分で実装できるか判断できない」という場合は、Fivenine Designにお気軽にご相談ください。要件の整理から実装・テストまで対応しています。

この記事をシェア

Webサイトの改善、お任せください

デザイン改善・表示速度向上・レスポンシブ対応など、成果の出るWeb制作を行います。 初回相談は無料です。

※ 1営業日以内にご返信いたします

この技術でお困りなら

無料でプロに相談できます

相談する
AIに無料相談