Youtubeとmp3の合わせ技

もくじ > Web開発ノート > リッチサイト+JavaScript

リッチサイトは音響や映像でユーザーを驚かせ、そして喜んでもらう事が目的です。例えばスマートフォンをいじっている時に、急に大きな音を出し、不本意にパケット消費させ、そして金銭を要求したり、この端末を壊すと脅迫するコンテンツであってはならないわけです。

そういった悪質なコンテンツから守るために、iOSやandroidなどのスマートフォンOSは頻繁に仕様変更しています。その仕様変更で悪質なコンテンツと共に、僕らが作るようなリッチサイトのコンテンツも動かなくなったりします。

ここでは新宿ウサギズのサイトを参考事例として、Youtube動画の埋め込みと、mp3音声ファイルの再生を活用したWebサイトの仕組みを紹介します。

新宿ウサギズの公式サイトでは、積極的に音や映像に関するWeb表現技術を取り入れてみています。そして、技術検証に役立てるために、あえて不完全なまま残してある部分もあります。

参考サイト『新宿ウサギズ』
https://usagiz.com

歌や映像も不完全な感じが新宿ウサギズらしさです。
読み応えある新宿ウサギズの歌メイキングはこちら。

以下のAPIの注意点

まず最初に、YouTube Player APIとWeb Audio APIを使った場合において、現在確認できている問題点をまとめてみました。

iOSではユーザーの許可なくして音を鳴らす事ができません。
ユーザーの許可とは「指でタップする事」です。つまりタップした瞬間でないと音を鳴らす事ができず、裏で勝手に音楽を変えたり、ゲームのような仕組みで効果音を鳴らすのは難しいかと思います。

この問題については、色んな対策方法がありました。一度タップすれば2回目以降は大丈夫とか、無音の音を鳴らしておけば、大丈夫とか。しかしandroidやPCにおいても、こういったユーザー許可制の仕組みの採用が急がれているようですので、先月までは大丈夫だったものが、OSのアップデートで急に動かなくなった。といった事態が増えていく事が予測されます。

ただ、工夫次第で音響を最大限に楽しめるコンテンツを作る事はできます。OSはインターネットから音を消し去りたいのではなく、あくまでもユーザーの意表を突いた悪質なコンテンツに対応するためのものですので、許可手続きとなる処理は用意される事が期待できます。


YouTube Player APIについて、iOSではsetVolume(n)で音量設定できないようです。
恐らく、iOSの音量設定はシステム側でコントロールの主導権を握っているからかもしれません。androidのほうがこのあたりは緩い感じがしますが、その分だけ、端末ごとの癖が強い傾向があります。

iOSではYoutubeをmute()すると、mp3まで音が出なくなります。
つまり無音Youtubeの上でmp3再生が不可能な状況です。解決方法が見つかる可能性はありますので、カラオケーモードではiOSでYoutubeの再生をしないようにしています。

複数の音声を再生させた場合、Web Audio APIで音量設定ができない。
恐らくこれは解決方法があります。現状、未解決のままなので、音量設定していません。

YouTube Player APIについて、iOSで動画をインライン再生させたい場合は、
playsinline: 1というパラメーターを付け加えてください。

Web Audio APIの音声の再生の終了判断について。
プログラムで「停止」した場合と、音声が最後まで再生された場合の判別ができないようです。それぞれのコンテンツに合わせた工夫が必要です。

ウサギズでは、Youtubeの再生終了を待つ処理と併用させています。

カラオケサイトを作る

このホームページでは、5曲の音楽の「歌付きバージョン」「カラオケバージョン」を切り替えて楽しむことができます。下に吸着する選曲ボタンを押して音楽を切り替え、上のブタがいるスイッチで歌とカラオケをスイッチします。

歌付きバージョンはYoutube動画をインラインフレーム(iframe)で埋め込んで再生させています。Youtubeの動画は、公式サイトの動画再生ページ内にある共有設定から埋め込み用のタグをコピーする事ができ、とても簡単にホームページに埋め込む事ができます。埋め込んだタグそのものは環境によって適当なフォーマットや再生画質などを自動選択するようになっており、タグさえ埋め込んでおけば、動画再生の技術の事は殆ど何も考えなくても、時代の変化に合わせてプログラム配布側で自動的に調整してもらえます。

カラオケバージョンはYoutube動画の音声をミュートし、動画再生の上に黒い網目模様を乗せて少し暗めに落として、歌詞のテキストを乗せています。音声はWeb Audio APIを活用し、mp3ファイルを再生させています。

こんなに面倒くさい事をせずに、歌のないカラオケ版のYoutube動画を用意し、mp3を使わない方法でも問題ないのですが、新宿ウサギズサイトではmp3とWeb Audio APIを使う事で、更に凝った参考事例をお見せします。

YouTube Player APIの特徴

Youtube動画をそのままインラインフレーム(iframe)で埋め込むのは、タグのコピペだけで済むので、とても簡単な事ですが、YouTube Player APIというもの合わせて使うと、少し複雑になりますが、Javascriptで以下のような事を制御できるようになります。

・自由なタイミングで動画の再生や停止、一時停止させる。
・動画の指定の時間から指定の時間までを再生する。
・ループや再生、自動再生、複数の動画の連続再生。
・再生する動画の自由なタイミングでの切り替え。
・連続再生の際、次に再生する動画を指定する。

・動画の再生速度を自由なタイミングで変更する。
・再生動画の画質の変更をプログラム的に行う事ができる。

・動画の再生サイズの変更。
・CSSと合わせてリアルタイムな変更も可能です。

・音量の設定。変更。活用するとフェードインフェードアウトなど。

・現在の再生時間の取得。
・動画の再生、停止など、状況のリアルタイムの把握。

正式名はYoutube IFrame Player APIです。
サンプルやリファレンスが充実した公式サイトはこちら。

ユーザーデータや動画情報を扱うYouTube Data APIとは別のものですので、ご注意ください。

YouTube Player APIの使い方

まずはYoutubeを埋め込みたい位置に以下のようなHTMLタグを配置します。
youtubeplayerというid名は他のものでも良いですが、この後のスクリプト内で指定する名前も合わせて変えてください。

<div id="youtubeplayer"></div>

次にJavascriptで以下の内容を実行してください。
これはAPIを読み込み、実行するための準備となります。

var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

無事にAPIが読み込まれると、自動的にonYouTubeIframeAPIReadyという関数が呼び出されます。
Javascript内にonYouTubeIframeAPIReadyを記述しておき、console.logなどで実行確認をしてみてください。

大抵、この関数の中には、プレイヤーの初期設定を行うスクリプトを書いておきます。

var player;
function onYouTubeIframeAPIReady() {
 player = new YT.Player('youtubeplayer', {
  height: '360',
  width: '640',
  videoId: 'r5YoQoWOMJE',
		playerVars: {
			autoplay: 1,
			loop: 0,
			controls: 0,
			showinfo: 0,
			playsinline: 1
		},
  events: {
   'onReady': onPlayerReady,
   'onStateChange': onPlayerStateChange
  }
 });
}

new YT.Playerにて、HTML内に設置したid名youtubeplayerのdivタグにプレイヤーを設置します。
その際、横幅や縦幅、再生するビデオのID番号などを指定する事ができます。

最低限必要なパラメーターはビデオIDくらいですが、自動再生、ループ、コントローラー表示などは、このようにplayerVarsという配列で指定できます。
eventsでは、再生可能になった時に実行する関数やら、再生ステータスが変更された場合に実行する関数やらを指定できます。

指定の動画ではなく、チャンネルを指定してまとめて連続再生させたい場合は、以下のように記述します。

var player;
function onYouTubeIframeAPIReady() {

    player = new YT.Player('youplayer', {
        playerVars: {
            autoplay: 1,
            listType: 'playlist',
            loop: 1,
            controls: 1,
            list: 'PLE5fNb-JoYVjo2luyA7P3YRXB7fhQoemy',
        }
    });
}
iOSで動画をインライン再生させたい場合は、playsinline: 1というパラメーターを付け加えてください。

一度、ここで設定したビデオは、以下のような実行文で制御できます。

player.playVideo();//ビデオの再生
player.pauseVideo();//ビデオのポーズ
player.mute();//ビデオの音声をミュート
player.unMute();//ミュートを解除
player.setVolume(0~100);//ビデオの音量設定

他にも色んな制御したり情報取得できます。公式のリファレンスをご覧ください。

わざわざIframeAPIを使う場合、色々複雑な事を期待するかと思います。例えば、複数の動画をJavascript側で切り替えたい場合、以下のような方法は色んな意味で間違えています。

player.stopVideo();//ビデオを停止する

//また新しくビデオを生成する
player = new YT.Player('youtubeplayer', {
 videoId: 'r5YoQoWOMJE',
	playerVars: {
		autoplay: 1,
	},
});

再生を停止する場合は、stopVideo()ではなく、pauseVideo()が推奨されているようです。stopVideo()は「ちょっと止めておく」とかいう程度ではなく、Youtube動画の制御そのものを破棄するそうです。なので、停止した後、また再生しようとしても、再生されません。

最初の一回、new YT.Playerを実行すると、YoutubeのAPIを実行する環境を整えるという意味があるそうなので、他のビデオを再生したい場合は、以下の1文だけで良いです。

player.loadVideoById('r5YoQoWOMJEz');

パラメーターをつける事もできますが、動画を切り替えるだけなら、これだけで良いです。
他、よく仕様変更されますので、公式サイトの説明ページをお見逃しなく。

Web Audio APIの特徴

ブラウザで音を鳴らす事ができるAPIです。音楽や効果音などをmp3ファイルなどで用意し、再生、一時停止、自由な時間位置から再生などを行う事ができます。

Web Audio API公式サイト。

このAPIは、ただ単に「音が出るマルチメディア」って感じではなく、オーディオ機能の追求をする研究者じみた姿勢が面白い仕組みです。普通に音を鳴らすだけでは使う事もなさそうな、多種多様なエフェクトや、波形の分析までできます。

技術者っぽさのある、ぶっきらぼうなAPIのため、数値設定や制御に癖があります。使いこなせば、誰も作った事がないような画期的なオーディオ表現ができるかもしれませんが、音を鳴らしたいだけでもちょっと苦労するかもしれません。以下に新宿ウサギズのソースコードを参考に、音を鳴らすまでの流れをまとめてみました。

このままソースコードをコピペしても動きません。少し複雑な理解が必要です。

Web Audio APIの使い方

まず、オーディオコンテキストを作成します。

window.AudioContext = window.AudioContext || window.webkitAudioContext;
sound_context = new AudioContext();

コンテキストってなんぞや?って時点で躓きますが、意味を調べると「文章などの前後の脈絡。文脈。」とありました。

次にコンテキストに音データをロードします。
新宿ウサギズでは5曲分のmp3をロードするため、ファイルを配列で指定しています。

sound_bufferLoader = new BufferLoader(sound_context,soundFileList,finishedSndLoad);
sound_bufferLoader.load();

new BufferLoaderってなんですか?って感じですが、これは用意されたものではなく、以下のような関数を作って実行させてます。

BufferLoader.prototype.loadBuffer = function(url, index) {

 var request = new XMLHttpRequest();
 request.open("GET", url, true);
 request.responseType = "arraybuffer";

 var loader = this;

 request.onload = function() {
  loader.context.decodeAudioData(
   request.response,
   function(buffer) {
    if (!buffer) {
     sound_err = 'unable buffer';
     return;
    }
    loader.bufferList[index] = buffer;
    if (++loader.loadCount == loader.urlList.length)
     loader.onload(loader.bufferList);
    },
   function(error) {
    sound_err = 'unable decode';
   }
  );
 }

 request.onerror = function() {
  sound_err = 'XHR error';
 }

 request.send();
}

複数の音声ファイルのロードに対応させるため、少し複雑に噛み合わせています。実際は以下のような文でロードさせています。

BufferLoader.prototype.load = function() {
 for (var i = 0; i < this.urlList.length; ++i)
 this.loadBuffer(this.urlList[i], i);
}

全ての音声のロードが完了したら、以下の関数を実行させています。

function finishedSndLoad(bufferList) {
 sound_buffers = bufferList;
}

繰り返しますが、このままコピペしても動きません。実際のソースコードではもっと色んな内部の都合をゴチャゴチャを書き込んでありますが、わかりにくいものを消して並べています。

ざっと目を通して、企画側にいる方が「なんとなくなるほど。こういうような理屈で作られるわけか」と理解するために、順を追って並べてみました。

そして、ここまでの流れは検索したりするとよく出てきますが、2018年末頃から以下の仕様が新たに追加されました。

$('.sound_resume').click(function () {
 sound_context.resume().then(() => {
  contentsInit();
 });
})

contentsInit()というのはこちらで用意した初期化用の関数です。
つまり、ユーザーの許可がないと音声の再生ができなくなりました。スマートフォンに限らず、ブラウザの規約変更により、時代は勝手に音を鳴らす事ができなくなる方向に進んでいます。

ここまできてやっと音の再生ができます。

var sndNum = $.inArray(sndName, soundNameList);
var source = sound_context.createBufferSource();
source.buffer = sound_buffers[sndNum];

ここまでがロードした音との接続です。
そして、以下の1文がWeb Audio APIならではの書き方です。

source.connect(sound_context.destination);

ロードしたオーディオソースを、サウンドコネクトのデスティネーションにconnect接続しています。
このconnectを繰り返す事で、必要な仕組みと仕組みをつなぎ合わせていきます。

例えば、音量を設定したい場合は、この後にこんなようなものを書きます。

sound_gainControl = sound_context.createGain();
sound_gainControl.connect(sound_context.destination);
source.connect(sound_gainControl);
sound_gainControl.gain.value = 0.5;

そして、以下のように書いて音声再生します。

source.loop = false;
source.start(0);

ループ設定を解除して、音声を0秒待ってから再生という意味です。

この後、音声を停止したりと制御するために、

sound_bgmSource = source;

このようにソースを記録して、以下のように制御します。

sound_bgmSource.stop();
sound_bgmSource = null;

ここまでの流れを読んだだけで、理解するのは厳しいかと思いますが、「なるほど面倒なんだな」という事がわかっていただけるだけでも十分です。その面倒な手続きをクリアすれば、以下のようなサイトが作れます。

https://usagiz.com
企画メイキングページ