導入ガイド

サイトにプレーヤーを設置する

スクリプトタグ1つで、動くプレーヤーが手に入ります。同じスクリプトがJavaScript APIも提供するので、用意されたレイアウトがデザインに合わなければ独自に作れます。再生もエピソード情報も試聴データもそのまま使えます。ビルド不要、フレームワーク不要、npmパッケージも不要です。

01プレーヤーIDを取得する

プレーヤーとは、埋め込む対象そのものです。ポッドキャスト全体、または特定のエピソードを指し示し、HTMLに貼り付ける公開IDを持ちます。

  1. 「アカウント」から「音声プレーヤー」を開きます。
  2. プレーヤーを作成し、自分でわかる名前を付けて、再生する内容を追加します。ポッドキャスト全体、1エピソード、または任意の順序で複数のエピソードを指定できます。
  3. プレーヤーIDをコピーします。

IDは公開される前提のものです。ページのソースに書かれ、誰でも読める状態になりますが、問題ありません。このIDが許可するのは1つだけ、そのプレーヤーに入れたエピソードを再生することです。APIキーではなく、アカウント内の他の情報には一切届きません。プレーヤーを無効にすれば、すべての場所で同時に無効になります。

後からエピソードを追加すると、そのプレーヤーのすべての埋め込みに即座に反映されます。貼り直しは不要です。

022行貼り付ける

スクリプトはページのどこに置いても構いません。divはプレーヤーを表示したい場所に置きます。

<script src="https://www.obbo.me/player/obboplayer.js" async></script>

<div data-obbo-player="YOUR_PLAYER_ID"></div>

これで導入は完了です。スクリプトは data-obbo-player 属性を持つ要素をすべて見つけて描画します。ページ上のプレーヤーが1つでも10個でも同じです。スクリプトを2回読み込んでも問題ありません。

誰かが再生を押すまで、何もダウンロードされません。プレーヤーは読み込み時にエピソード一覧を取得しますが、実際に誰かが再生を始めるまで音声は要求されません。ページにプレーヤーを置いても訪問者に負担はかからず、実際には起きていない再生が記録されることもありません。

03レイアウトを選ぶ

7種類が用意されています。data-obbo-ui で指定してください。省略すると、プレーヤー作成時に選んだものが使われるので、ページを編集せずに後から変更できます。

値内容
list操作部の下にエピソード一覧が並びます。ポッドキャスト全体を見せたいときや、最新以外のエピソードも聴かれそうなときに。
bar操作部のみ。ひとつのエピソードを中心にした記事や製品ページに。
cardカバーアートを大きく見せ、ボタンをその縁に置きます。サイドバーや一覧など、横長のバーでは取ってつけたように見える場所に。
inline再生ボタンと時間表示だけ。文章を分断せず、文の中に収まる大きさです。
dock再生を押すと画面下部に固定されるので、スクロールしても操作部が流れていきません。閉じることもできます。
transcriptエピソードを、音声に追従する読めるテキストとして表示します。話している行がハイライトされ、任意の行をクリックするとその位置へ移動します。
auto既定値。プレーヤーに保存されたレイアウトに従います。
<div data-obbo-player="YOUR_PLAYER_ID"
     data-obbo-ui="list"
     data-obbo-theme="dark"></div>

すべての属性

data-obbo-player必須。プレーヤーIDです。
data-obbo-uilist、bar、card、inline、dock、transcript、または auto。
data-obbo-themelight または dark。
data-obbo-episode特定のエピソードから始めます。選択されるだけで、再生はされません。
data-obbo-isolateshadow root の中に描画し、ページのCSSから切り離します。
data-obbo-localeプレーヤーの操作部分の言語です。en、es、da、ja。エピソードのタイトルはいずれの場合もポッドキャストの内容が使われます。
data-obbo-language再生する音声の言語: en、es、da、ja のいずれか。既定ではページ自身の lang を使用し、その言語の音声がないエピソードは元の言語で再生します。
data-obbo-autoadvanceエピソードが終わったら次を再生します。

1ページに複数のプレーヤーを置いても問題ありません。エピソード一覧の取得は1回にまとめられ、ひとつを再生すると他は一時停止します。訪問者が2つの音声を同時に聞くことはありません。

04サイトになじませる

プレーヤーは閉じたフレームではなく、ページ自身のDOMに描画されます。スタイルシートが届き、フォントも継承されます。

手早く: 9つの変数

--obbo-player-bg#ffffff
--obbo-player-fg#1c2733
--obbo-player-muted#66768a
--obbo-player-accent#2f6fed
--obbo-player-accent-fg#ffffff
--obbo-player-border#e3e8ef
--obbo-player-row-hover#f4f6f9
--obbo-player-radius12px
--obbo-player-fontsystem stack
.my-player {
  --obbo-player-accent: #e5484d;
  --obbo-player-radius: 4px;
  --obbo-player-font: 'Söhne', system-ui, sans-serif;
}

@media (prefers-color-scheme: dark) {
  .my-player {
    --obbo-player-bg: #181d24;
    --obbo-player-fg: #f2f5f8;
  }
}

しっかり: クラス名

各要素には obbo- で始まる安定したクラス名が付いており、こちらが提供するルールはすべて単一クラスセレクタです。そのため !important なしでご自身のルールが優先されます。ルート要素には data-obbo-state も付くので、内部構造を知らなくても読み込み中やエラー時の見た目を調整できます。

.obbo-pプレーヤーのルート。data-obbo-state が付きます。
.obbo-play再生・一時停止ボタン。
.obbo-seekシークバー。type が range の input です。
.obbo-title / .obbo-subエピソードのタイトルとポッドキャスト名。
.obbo-artカバー画像。ポッドキャストに画像がない場合は非表示になります。
.obbo-list / .obbo-rowエピソード一覧とその行。再生中の行には aria-current が付きます。
.obbo-note読み込み中・エピソードなし・エラーのメッセージ。
.obbo-p[data-obbo-state="loading"] { opacity: 0.6; }
.obbo-p[data-obbo-state="error"]   { display: none; }

CSSの影響が強い場合は切り離してください。強力なフレームワークのリセットはプレーヤーにも届きます。data-obbo-isolate を追加すると shadow root 内に描画され、スタイルシートの影響を受けません。9つの変数は引き続き有効なので、色の調整は残り、クラス単位の調整だけができなくなります。

05独自のプレーヤーを作る

用意されたレイアウトを一切使わない方法です。createPlayer がエピソード情報、再生、再生位置を渡すので、マークアップはご自身で書けます。試聴の記録も、期限切れになった音声リンクの復帰も、そのまま働き続けます。下にある操作部は、その下のコードが実際に動いているものです。

<button id="play" disabled>Play</button>
<span id="title"></span>
<div id="track"><div id="fill"></div></div>

<script>
  var player = Obbo.createPlayer({ widgetId: 'YOUR_PLAYER_ID' });

  player.subscribe(function (state) {
    play.disabled     = state.status !== 'ready';
    play.textContent  = state.playing ? 'Pause' : 'Play';
    title.textContent = state.episode ? state.episode.title : '';
    fill.style.width  = (state.position / state.duration * 100) + '%';
  });

  play.onclick = function () { player.toggle(); };
</script>

status が ready になるまでボタンは無効にしてください。iOSでは、音声を再生する許可はそれを求めたタップに結び付いています。プレーヤーがまだ応じられないタップは無駄になり、訪問者はもう一度タップすることになります。

06音声に追従する文字起こし

文字起こしレイアウトは、エピソード全体をテキストとしてページに置き、音声と歩調を合わせます。Obboのエピソードは制作の一環として文単位のタイムスタンプ付きで書き起こされるため、準備は不要です。

  • 話している行がハイライトされ、自動で見える位置までスクロールします。読者が自分でスクロールすると追従を止め、戻るためのボタンを出します。スクロールを奪い合うことはありません。
  • 任意の行をクリックするとそこから再生されるので、エピソードが引用可能になります。読者は目当ての一文を見つけて、それが語られるのを聴けます。
  • テキストはページ内の本物のテキストなので、検索エンジンが索引化し、スクリーンリーダーが読み上げます。そのままでは中身の見えない音声の箱だったエピソードが、テーマに関するコンテンツになります。
<div data-obbo-player="YOUR_PLAYER_ID"
     data-obbo-ui="transcript"></div>

まだ有効になっていません。文字起こしは全エピソードで生成されていますが、埋め込みAPIではまだ公開していないため、現時点ではテキストの代わりに注記を添えたプレーヤーが表示されます。こちらでフィールドを有効にすれば自動的に表示されます。ページ側の変更は不要です。

07APIリファレンス

Obbo.createPlayer(options)

widgetId必須。プレーヤーIDです。
locale組み込みラベルの言語。既定は en。
autoAdvance次のエピソードへ続けます。既定はオフ。
exclusive再生開始時にページ上の他のプレーヤーを一時停止します。既定はオン。

プレーヤー

subscribe(fn)状態が変わるたびに全体の状態とともに呼ばれ、登録直後にも一度呼ばれます。解除用の関数を返します。
getState()現在の状態。購読ではなく都度取得したい場合に。
play(episodeId?)再生を開始します。先にエピソードを切り替えることもできます。
pause() / toggle()見たままの動作です。
select(episodeId)再生せずにエピソードを切り替えます。通信は発生しません。
seek(seconds)指定した位置(秒)へ移動します。
setRate(rate)再生速度。0.75 から 2 まで。
next() / previous()エピソード一覧を前後に移動します。
reload()エピソード一覧を取得し直します。
destroy()停止して切り離します。コンポーネントのアンマウント時に呼んでください。
media内部の audio 要素です。想定外の用途が必要になった場合に。

状態

statusloading、ready、empty、error のいずれか。
error空、またはタップなしでブラウザが拒否した blocked、load_failed、IDが誤っているか失効している unavailable。
episodesプレーヤー内のすべてのエピソード。タイトル、説明、番号、日付、長さ。
episode選択中のエピソード。選択は再生を意味しません。
podcastタイトル、会社名、カバー画像。
playing現在音声が流れているかどうか。
position / durationいずれも秒単位。
playable選択中のエピソードが再生可能かどうか。

Obbo のその他

Obbo.formatTime(s)65 を 1:05 に変換します。組み込みレイアウトと同じ形式です。
Obbo.ui.list(el, player, opts)組み込みレイアウトを自分で設置します。destroy を持つオブジェクトを返します。
Obbo.mount(root?)プレーヤー要素を探して設置します。マークアップを動的に追加した場合に呼んでください。
Obbo.unmountElement(el)ひとつを取り外します。
Obbo.version不具合の報告時に添えてください。

08React、Vue など

自動スキャンに頼らないでください。コンテナを再描画するフレームワークはプレーヤーのマークアップを消し去り、イベントリスナーだけを残します。自分で設置し、アンマウント時に破棄してください。設置先の要素には自分で何も描画しないでください。

function Player({ widgetId }) {
  const host = useRef(null);

  useEffect(() => {
    const player = Obbo.createPlayer({ widgetId });
    const ui = Obbo.ui.list(host.current, player);
    return () => { ui.destroy(); player.destroy(); };
  }, [widgetId]);

  return <div ref={host} />;
}

スクリプトは、エフェクトが動く前に一度だけ、document の head か小さなローダーで読み込んでください。Vue の onMounted と onUnmounted、Svelte の onMount でも同じです。

09Content-Security-Policy

サイトが Content-Security-Policy ヘッダーを送っている場合、3つのディレクティブが必要です。

script-src https://www.obbo.meプレーヤーの読み込みに必要です。
connect-src https://app.obbo.meエピソード一覧の取得に必要です。
media-src https://app.obbo.me https://*.amazonaws.com音声そのものの配信に必要です。

見落とされるのは media-src です。音声はAPIとは別のホストのストレージから配信されます。これを省くと、プレーヤーは一見まったく正常に見えたまま、誰かが再生を押した瞬間に失敗します。

10記録される内容

サイトでの試聴は Obbo の分析に表示されます。プレーヤーが読み込まれた回数、再生が始まった回数、そして実際に聴かれた量です。

  • 訪問者が特定されることはありません。アカウントもログインも不要で、プレーヤーはCookieを設定しません。レポートは所有者であるあなたに紐づき、聴いている人には紐づきません。
  • ページの表示は再生ではありません。音声が実際に始まるまで、再生としては数えられません。
  • 試聴時間は推測ではなく計測です。early飛ばした部分は聴いたことになりません。

11うまくいかないとき

何も表示されない

要素に data-obbo-player とご自身のIDが指定されているか、スクリプトタグがページにあるかを確認してください。ページ読み込み後にマークアップを追加した場合は、そのあとで Obbo.mount() を一度呼んでください。

プレーヤーを利用できないと表示される

IDが誤っているか、アカウントでプレーヤーが無効化されています。どちらの場合も意図的に同じメッセージを返します。

エピソードがないと表示される

プレーヤーが空か、エピソードがまだ制作中です。完成したエピソードだけが配信されます。

見た目は正常なのに再生されない

ほとんどの場合、Content-Security-Policy に media-src がありません。それ以外の場合は state.error を確認してください。blocked は、ブラウザが実際のタップを先に求めたという意味で、クリック以外の操作で再生を開始したときに起こります。

見た目が崩れる

ページのCSSがプレーヤーに届いています。通常はそれが狙いですが、そうでない場合は data-obbo-isolate を追加して切り離してください。

それでも解決しない場合ページのアドレス、プレーヤーID、Obbo.version の値を添えてご連絡ください: hello@obbo.me

すべてのレイアウトを実際に見る

ポッドキャストプレーヤーをサイトに設置する