WIZ-CODE.blog

JavaScriptやAjaxをテーマとしたブログです。

*

SoundJSのロード機能を使った簡易音楽ファイルローダー

      2016/02/08

ブラウザで音楽を再生する有効な手段は、5年くらい前まではほとんどFlashくらいしかありませんでした。IEFirefoxは独自に音楽再生用のタグを用意していたものの、どちらも使い勝手が悪いものでした。いまは少なくなりましたが、「(勝手に)音が鳴るサイト」のほとんどはIEのBGSOUNDを使用しているサイトです。

そうしたなか、HTML5の登場で、Webサイトがただの文書置き場ではなく、アプリケーションとして振舞うことを意義付けられると、動画にしろ音楽にしろプラグインなしでインタラクティブなユーザ体験が実現できないのはおかしいだろうという風潮になり、HTML5規格の策定事業のうちメディア関連の成果としてAUDIO/VIDEO要素が誕生しました。IEもバージョン9からこれらのメディア要素を実装しています。

しかし、こうした新規格の導入後もなぜかブラウザ間でサポートの遅れや挙動の違いが現れ、モバイルにしても機能の制限やバグが多く、Web上ではこれらの実装に苦労した話がよくでてきます。ユーザーや開発者としては、すべてのブラウザで音を鳴らせられれば、ようはそれでいい話なのですがね。

デベロッパーと思われる方のこんな記事があります。
Takazudolog – 地獄のvideo/audio要素

音楽関係にしぼって話しますが、これらAUDIO要素にまつわる諸々の問題を解決するライブラリがいくつかあり、そのひとつにSoundJSがあります。このライブラリは、WebページをリッチなコンテンツにするためのJavaScriptライブラリであるCreateJSライブラリのいわばサウンド部門です。さらにWebAudio API経由でエフェクトをつけたり高精度でサウンドの制御ができたりします。

とりあえずWeb上で音楽を鳴らしたいだけであれば、上記のような事情からHTML5のネイティブなAPIを使用するより、SoundJSのようなライブラリを経由した方が、安定かつ手早くていいでしょう。自分も以前SoundManager2というFlashを使用したライブラリで簡易音楽プレーヤーを作ったことがあります。SoundManager2ライブラリは配布元サイトが現在も更新されていてすでにHTML5対応になっていますが、自分は現在このSoundJSの使い方を勉強しています。

SoundJSはローダーと音楽制御APIを備えたライブラリです。自分は今回、このライブラリを使ってより簡易に扱える音楽ファイルローダーSoundLoader.jsを作成しました。

jQueryプラグイン SoundLoader.js (6KB)(jQuery、Underscore.js、SoundJSが必要です)

SoundJS自体がローダーなのになぜ、それをベースにローダーを作ったのかというと、そもそもこのライブラリのAPIがローレベルで、音楽ファイルをロードして再生する単純なことをするにも少し手間がかかると思ったからです。実際、このライブラリを使いこなすまでに至るには結構勉強コストがかかると感じました(SoundJSの配布サイトが英語であるのも理由のひとつですが)。その他にSoundJSの複雑さからわかりにくいと思われる点が多くあります。たとえば音楽を再生するのに、SoundJSはインスタンス経由で再生するのと静的メソッドであるcreatejs.Sound.play()を使用するふたつの方法を用意していますが、両者は何が違うのかなどです。など、細かい挙動の違いを知るには直接コードを読まないといけないところが多々あります。SoundJSの学習中に気づいた疑問点などは逐一解説していく予定です。

今回作成したSoundLoader.jsは、jQueryDeferred機能とUnderscore.jsのメソッド群を使用。そのためスクリプトは単独で動作せず、これらのライブラリに依存します。また、SoundJSについて自分は最新バージョンの0.6.2を使用したので、以後はこのバージョンを前提にした話しになります。

SoundJSライブラリを使用するには、配布サイトにてダウンロードするか、CDNを利用します。開発環境用にダウンロードしたライブラリを使用し、リモートではCDNサービスを利用するといった使い分けができます。なお、CreateJSにはPreloadJSという専用ローダーがありますが、SoundJS単体でも音楽ファイルに限定するならばロードができます。ローカルで読み込むべきファイルは、ダウンロードしたSoundJSライブラリのlibフォルダ内にあるsoundjs-0.6.2.combined.jsです。

SoundLoader.jsはSoundJSで音楽ファイルをロードする手続きを簡略化します。例えて言うならば、jQueryの$.ajax()に対する$.post()$.getJSON()のようなものです。$.ajax()を使うには細かい設定が必要で手間がかかりますからね。

SoundJSをそのまま使ってロードする方法と、SoundLoader.jsを使う方法を比べて見ていきましょう。

SoundJSでひとつのファイルを登録する方法

//第1引数:ファイルURL 第2引数:サウンドID(省略可)
createjs.Sound.registerSound('/sound/mySound.ogg', 'soundID');

SoundJSで複数のファイルを登録する方法

//複数登録の場合、createjs.Sound.registerSounds()メソッドを使う
createjs.Sound.registerSounds([
    {id: 'soundID1', src: '/sound/mySound1.ogg'},
    {id: 'soundID2', src: '/sound/mySound2.ogg'}
]);

//パスを指定して次のように書くことが可能
createjs.Sound.registerSounds({
    path: '/sound/',
    manifest: [
        {id: 'soundID1', src: 'mySound1.ogg'},
        {id: 'soundID2', src: 'mySound2.ogg'}
    ]
});

createjs.Sound.registerSound()を実行すると、SoundJSは音楽ファイルのロードを開始します。しかし、ユーザーはいつ音楽が再生可能になるかを知る必要があるので、次のようにイベントリスナーを登録して、ロード完了した音楽ファイルを補足します。

//ファイルが複数あっても、イベントリスナーはひとつ呼び出すだけでよい
createjs.Sound.on('fileload', function (event) {
    console.log(event.id + 'はロードが完了しました。');
    //音を鳴らしてみる
    createjs.Sound.play(event.id);
});

createjs.Sound.on(‘fileload’, fn)あるいはcreatejs.Sound.addEventListener(‘fileload’, fn)は、ロード完了したすべてのファイルを順不同に補足します。そのため、ファイルごとに固有のコールバックを指定するといったことができません。コールバックが呼ばれてから、イベントオブジェクトを調べて初めてどのファイルが完了したかが分かるのです。

SoundLoader.jsの使い方

SoundLoader.jsjQueryのプラグインとして動作します。jQuery.soundLoader()または$.soundLoader()と記述します。このプラグインはPromiseオブジェクトを返り値とし、ロード成功時と失敗時で分岐させることができます。Deferredについては前回取り上げました。

//$.soundLoaderはPromiseオブジェクトを返す。そのため、Then()やDone()メソッドにつなぐことができる
$.soundLoader('/sound/mySound.ogg').
    done(function (loadedFiles, errorFiles) {
        //loadedFilesは配列。ロード完了したファイルがすべて入れられる
        //loadedFiles => [{id: 'mySound', src: '/sound/mySound.ogg'}]
        createjs.Sound.play(loadedFiles[0].id);
    }).
    fail(function (errorFiles) {
        //Do something...
    });

SoundJSとの違いは、SoundJSがひとつのファイルごとにコールバックが呼び出されるのに対し、SoundLoader.jsは指定したファイルすべてがロード完了したとき一度だけ呼び出される点です。成功時のコールバックの第1引数にロードに成功したファイルデータが配列形式で渡され、第2引数に何らかの原因でロードに失敗したファイルデータが渡されます。一方で、失敗時のコールバックが呼ばれる条件は、指定したすべての音楽ファイルがロードに失敗したときです。その際、第1引数に失敗したファイルデータが渡されます。

//一度変数に入れておいて、別のタイミングで分岐処理をすることも可能
var deferred = $.soundLoader('/sound/mySound.ogg');

deferred.done(function (loadedFiles, errorFiles) {
    createjs.Sound.play(loadedFiles[0].id);
});

それでは、どのような形式データをSoundLoader.jsに渡せばよいでしょうか。上の例ではもっともシンプルな、ファイルURLの文字列だけを渡すものでした。SoundJSの指定方法よりも簡易になっています。

//(1)音楽ファイルURLのみ
var source = '/sound/mySound1.ogg';
var deferred = $.soundLoader(source);

//(2)複数の音楽ファイルURLを配列にする
var source = [
    '/sound/mySound1.ogg',
    '/sound/mySound2.ogg',
    '/sound/mySound3.ogg'
];

SoundLoader.jsはSoundJSでの指定方法と異なり、IDを指定できません。というのも、SoundLoader.jsは渡された音楽ファイルURLのファイル名(拡張子除く)のみを抜き出し、自動的にそれをIDにするからです。URLが/sound/mySound1.oggであればIDはmySound1になります。SoundLoader.jsにはIDとは別にName属性を指定できます。これは必須の要素ではありませんが、ロード完了後に渡されるファイルデータに紐付けることができます。この場合、文字列ではなくName属性値を第1要素、URLを第2要素とした配列を指定します。(1)、(2)のようにName属性が指定されていない場合、IDと同じ値になります。

//(3)Name属性と音楽ファイルURL
var source = [
    ['マイサウンド1', '/sound/mySound1.ogg'],
    ['マイサウンド2', '/sound/mySound2.ogg'],
    ['マイサウンド3', '/sound/mySound3.ogg]'
];
$.soundLoader(source).done(function (files) {
    console.log(files[0].name); //"マイサウンド1"と出力される
});

音楽ファイルが特定のディレクトリに集められているならば、次のように$.soundLoader()の第2引数にパスを指定できます。このとき音楽ファイルのURLはファイル名と拡張子のみに省略します。

//(4)パスの指定
var path = '/sound/';
var deferred = $.soundLoader('mySound.ogg', path);

//(5)複数のパスを指定したいときは、別々にプラグインを呼び出す
var path1 = '/sound/';
var path2 = '/bgm/';
var deferred1 = $.soundLoader('mySound.ogg', path1);
var deferred2 = $.soundLoader('myBgm.ogg', path2);

オーディオスプライトやチャネル数の指定など、SoundJSの高度な機能をそのまま使用できます。

//(6)高度な機能の利用
var soundFiles = [
    //オーディオスプライトの書式
    //配列の第1要素に音源となるファイルURLを指定する
    //第2要素にハッシュ形式でSoundJSと同じ書式で詳細を指定
    [
        "sound-1.mp3",
        {
            audioSprite: [
                {id: "audio-sprite1", startTime: 0, duration: 1000}
                {id: "audio-sprite2", startTime: 1500, duration: 1000}
            ]
       }
    ],
    //配列として指定する場合、第3引数に最大チャネル数を指定できる
    ["サウンド2", "/audio/bgm/sound-3.mp3", 5]
];

SoundJSで事前に設定しておくこと

かつては各ブラウザがサポートする音楽ファイル形式がバラバラなため、AUDIO要素で音楽再生を試みるとなると、フォールバック用のファイルが必要でした。現在の状況はというと、IE11がいまだWAVEOgg vorbisをサポートしていないのを除くと、他の代表的なフォーマットであるMP3AACは主要ブラウザでサポートされています。が、対策がまったく不要というわけではありません。可能であればOggで再生し、ダメならMP3というのはよくあるパターンです。でも、SoundJSにはこのようなフォールバックのためのAPIが用意されています。音楽ファイルをロードする前に次のプロパティを指定するだけです。

//最初のファイルがダメなら、同名のMP3とWAVEファイルを探しに行く
createjs.Sound.alternateExtensions = ['mp3', 'wav'];

SoundクラスのalternateExtensionsプロパティは配列を指定します。もし、最初に登録した音楽ファイルが現在のSoundJSプラグインで再生できないときは、alternateExtensionsプロパティで指定されたファイル形式を順番に試していきます。このとき、フォールバック用の同名ファイルが存在することが前提になります。

SoundJSは音楽ファイルを読み込み、再生する機構(プラグイン)を三つ用意しています。HTMLAudioPluginWebAudioPluginFlashAudioPluginです。なお、FlashAudioPluginは別途Flashファイルを用意する必要があり、先に挙げたsoundjs-0.6.2.combined.jsファイル単体では動作しません。SoundJSはユーザーのブラウザ環境によって柔軟にこれらのプラグインを切り替える仕組みを提供していて、デフォルトではWebAudioPluginを優先して使う仕様になっています。音楽ファイルをロードする前に、createjs.Sound.registerPlugins()メソッドで優先するプラグインを次のように変更することができます。

//配列で指定する。このケースはWebAudioPluginを最初に試し、次にHTMLAudioPlugin、最終的にFlashAudioPluginを適用する
createjs.Sound.registerPlugins([
    createjs.WebAudioPlugin,
    createjs.HTMLAudioPlugin,
    createjs.FlashAudioPlugin
]);

ところで、WebAudioPluginは高性能ですが、問題がないわけではありません。WebAudio APIの仕様がもともと再生時間の短い(45秒程度)音楽ファイル向きであることから、数分の長さにわたる楽曲を再生するには不向き(デコードに失敗するケースがあります)であるからです。また、WebAudio APIはXHRを通じて音楽ファイルを取得し、ロードが完全に終了するまで音楽データの解析ができません。一方のHTMLAudioPluginであればストリーミング再生が可能です。SoundLoader.jsはBGMと効果音の再生をどちらも支障なくできるよう、あらかじめプラグインがHTMLAudioPluginに指定されています。一方で、音声のビジュアライズを行うときや、ロードするのが効果音などの短いファイル中心のときは、WebAudioPluginに切り替えた方がいいかもしれません。もうひとつ注意点として、createjs.Sound.registerPlugins()メソッドは最初に一度だけしか呼び出せないことです。たとえばBGM用の音楽ファイルをロードするときだけHTMLAudioPluginにして、効果音ファイルのロード時はWebAudioPluginにするということはできません。どうもcreatejs.Sound.registerPlugins()を呼び出すと、内部の_SoundInstancesを初期化するらしく、それ以前のデータがなくなってしまうからです。

音楽を再生するときの注意点

SoundLoader.jsが提供するのは音楽ファイルのロードのみなので、それ以降の音楽の再生/停止などの操作はSoundJSのAPIを直接たたいていくことになります。先ほど挙げたようにSoundJSで音楽を再生する方法はふたつあります。

//静的メソッドを使って再生する
createjs.Sound.play('soundID', config);

//インスタンスのメソッドで再生する
var instance = createjs.Sound.createInstance('soundID');
instance.play(config);

静的メソッドとインスタンスメソッドの使い分けですが、前者は単発の使い捨てとして、後者は再利用や連続使用を視野に入れた使い方になります。静的メソッドも内部ではすべてインスタンス経由で再生されます。もし、オーディオを完全に制御したいのであれば、これらのインスタンスを変数に入れて、適切に追跡する必要があるでしょう。なお、createjs.Sound.play()メソッドもcreatejs.Sound.createInstance()と同じようにインスタンスを返します。これらのインスタンスは、同じ音源ファイルでも複数生成されます。それぞれの音源ファイルには、チャネル上限数が設定されますが、インスタンス自体はその上限を超えて生成されます。同一音源の同時再生数がチャネル上限数を超えると、以降の同一音源の再生は制限を受けます。再生操作の制限を受けるとき、ユーザーはインスタンスのコンフィグ経由で、そのとき停止するインスタンスと再生するインスタンスをcreatejs.Sound.INTERRUPT_ANYのような定数値で制御することができます。

//個別にもしくは一括してコンフィグを設定できる
var config = new createjs.PlayPropsConfig().set(
    {
        //同一音源の同時再生数がチャネル上限数を超えたとき、
        //現在再生中のいずれかのインスタンスを停止する
        interrupt: createjs.Sound.INTERRUPT_ANY,

        //再生回数の指定。-1にするとループ再生する
        loop: -1,

        //インスタンスの再生音量。マスターボリュームはcreatejs.Sound.volumeで指定する
        volume: 0.5
    }
);

instance.play(config);

//後から設定を変えたいときはインスタンスのapplyPlayPropsメソッドを使うか、プロパティに指定する
instance.volume = 0.8;

interruptプロパティですが、createjs.Sound.INTERRUPT_LATEにすると、一番早く再生したインスタンスから停止し、ちょうど先入れ先出しの形になるので自然な設定でよいかと思います。最後に音楽の停止方法を見ていきます。

//静的メソッドのcreatejs.Sound.stop()は引数を指定しません。これを呼び出すと現在再生中のすべてのインスタンスを停止させます。
createjs.Sound.stop();

//インスタンスのメソッドで停止する、より適切な停止方法
instance.stop();

 - JavaScript/Ajax, ブラウザ , , , ,