latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

UnitePlayer.js 作ったよ

UnitePlayer はモバイルとゲームに特化した HTML5 な音楽再生プレイヤー

UnitePlayer は、モバイルブラウザ上で動作するゲームに音をもたらします。
扱いが難しい Mobile SafariAndroid ブラウザの音周りをフォーマット化し、とっても扱いやすくします。

フィーチャーフォン用のソーシャルゲームが大流行な昨今ですが、
フィーチャーフォン用のゲームって音が無いですよね?
そのゲームをそのままスマートフォン向けにコンバートしても、音がならずに寂しい感じですよね?
2012年は UnitePlayer で BGM も SE も鳴らしちゃいましょう。
そして没入感や色々なものを高めちゃいましょう!

PCブラウザでもそのまま動くから、横展開もお手軽に!

UnitePlayer なら iPhone でも BGM と SE を擬似的に同時再生できますよ。

UnitePlayer API リファレンス (version 2.0)

http://code.google.com/p/uupaa-js-spinoff/wiki/UnitePlayer_API

UnitePlayer ソースコード

ソースコード (minified)

音源の用意から再生までの流れ

ざざっと音源を1つのファイルにまとめます。

  • 音源の用意
  • 音源の配置(マッピング)
  • 音源を mp3 と ogg で書き出し
  • 音源マップをjsで記述
音源の用意

まず、適当な音源を用意します。
UnitePlayer が想定している音源は3種類。BGM, ジングル/アイキャッチ(ちょっと長めの効果音), SE(効果音) です。

  • BGM は自動的にループ再生します。ループしている音源を用意すると良いでしょう
  • SE には、余韻を長引かせた音ではなく、スタッカートの効いた歯切れの良い音が良いでしょう
    • Mobile Safari(iPhone)では BGM を止めて SE を鳴らすため、余韻が長いとその分 BGM の再生が遅れてしまいます
  • ステレオとモノラルの混在は避けましょう。どちらかに統一します
  • BGM を控えめにする
    • Android では BGM と SE を同時に再生できますが、BGM と SE の音量だけを個別に制御できません
    • あらかじめ BGM のレベルを絞り、SE と同時に再生しても SE が判別できる状態にしておくとよいでしょう

これだと BGM のレベルが大きすぎて SE が隠れるため
f:id:uupaa:20111212215317p:image

BGM のレベルを SE よりも控えめに
f:id:uupaa:20111213143145p:image

音源の配置(マッピング)

Audacity などの編集ソフトで、あつめた音源を1つにします。

ちょっとした決まりごとがあります。

  • 先頭15秒と、末尾5秒を無音にします
  • BGM, ジングル, SE の順に配置します。長く,ループする音源を先に配置します
  • 各音源の間には、5秒以上の無音時間を入れます

f:id:uupaa:20111213143145p:image


配置が終わったら mp3 と ogg ファイルに書き出します。

音源を mp3 と ogg での書き出し

mp3 と ogg で書き出します(oggFirefox用です)。
ファイルサイズやダウンロード時間を考慮し、大きくても1.5MBぐらいに収めるとよいでしょう。
128kbps もあれば十分高音質なのですが、 32~64kbps ぐらいでもなんとかなります。

音源マップを js で記述

次に音源マップを js で記述します。

以下は、BGM x 2, ジングル x 3, SE x6 の配置例です。

  • 配置時間を "00:00:00.000" 形式の文字列か、12345.678 の数値形式で指定していきます
  • BGM なら配列の三番目に true を指定します
  • volume は指定可能ですが、iPhoneAndroid では効きません(PCブラウザ用の設定です)
  • offset に 0 以外の値を指定すると preset: {...} で指定した時間を一括でずらす事ができます
  • 開始時刻 >= 終了時刻となるようなデータは指定できません
var param = {
        mp3:    "uni.mp3", // mp3ファイル
        ogg:    "uni.ogg", // oggファイル(省略可能)
        volume: 0.5,       // 初期ボリューム
        offset: 0,         // マッピングのオフセット値(省略可能)
        preset: {          // ここに座標データを置いていきます
            // プリセット名 [開始時刻,  終了時刻,   BGMならtrue]
            BGM0:           ["0:00",    "0:10",     true], // 先頭の無音時間を無音BGMに応用
            BGM1:           ["0:15",    "1:28",     true],
            BGM2:           ["1:35",    "2:38",     true],
            Z_before_boss:  ["3:01",    "3:04.5"],
            Z_after_boss:   ["3:09",    "3:13.6"],
            Z_gacha:        ["4:45",    "4:53.0"],
            SE_001:         ["3:17",    "3:17.5"],
            SE_002:         [  201 ,      203.0 ], // 数値でも指定可能
            SE_003:         ["3:25",    "3:28.2"],
            SE_004:         ["3:31",    "3:31.6"],
            SE_005:         ["3:35",    "3:35.3"],
            SE_006:         ["3:39",    "3:40.8"],
        }
    };
鳴らしてみましょう
<!DOCTYPE html><html><head><meta charset="UTF-8" />
<title>UnitePlayer DEMO</title>
<style>.pconly{border:1px solid orange}</style>
<script src="uni.js"></script>
<script>
document.write(navigator.userAgent);

window.onerror = function(msg, file, line) {
    alert("ERROR: msg=" + msg +
              ", file=" + file + ", line=" + line);
};

window.track0 = null;
window.track1 = null;
window.track2 = null;

function log(args) {
    var id = "logger", d = document, n,
        parentNode = d.getElementById(id) ||
                     d.body.appendChild(n = d.createElement("div"),
                                        n.id = "logger", n);
        node = d.createElement("div");

    if (parentNode.childNodes.length > 100) {
        parentNode.innerHTML = "";
    }
    node.textContent =
        Array.prototype.slice.apply(arguments).join(" ");
    parentNode.appendChild(node);
}

function init(iOSSimulate) {
    if (!window.HTMLAudioElement ||
        !window.HTMLAudioElement.UnitePlayer) {

        alert("Need iOS 4.0 and later, Android 2.3 and later");
        return;
    }
    if (window.track0) {
        return;
    }

    var param = {
            mp3:    "uni.mp3",
            ogg:    "uni.ogg",
            volume: 0.5,
            offset: 0,
            preset: {
                BGM0:               ["0:00", "0:10", true],
                BGM1:               ["0:15", "1:28", true],
                BGM2:               ["1:35", "2:38", true],
                Z_before_boss:      ["3:01", "3:04.5"],
                Z_after_boss:       ["3:09", "3:13.6"],
                Z_gacha:            ["4:45", "4:53.0"],
                SE_001:             ["3:17", "3:17.5"],
                SE_002:             ["3:21", "3:23.0"],
                SE_003:             ["3:25", "3:28.2"],
                SE_004:             ["3:31", "3:31.6"],
                SE_005:             ["3:35", "3:35.3"],
                SE_006:             ["3:39", "3:40.8"]
            }
        };

    var UnitePlayer = HTMLAudioElement.UnitePlayer;

    window.track0 = new UnitePlayer(param, 0,
                                    function(evt, that, track, time) {

                log("track" + track, evt.type, time.toFixed(3));
            });

    if (!iOSSimulate) {
        if (window.track0.info().unite) {
            // single audio
            ;
        } else {
            // multi audio
            window.track1 =
                    new UnitePlayer(param, 1,
                                    function(evt, that, track, time) {
                log("track" + track, evt.type, time.toFixed(3));
            });
            window.track2 =
                    new UnitePlayer(param, 2,
                                    function(evt, that, track, time) {
                log("track" + track, evt.type, time.toFixed(3));
            });
        }
    }
}
function preset(name, track) {
    switch (track) {
    case 2: if (window.track2) {
                window.track2.preset(name);
                break;
            }
    case 1: if (window.track1) {
                window.track1.preset(name);
                break;
            }
    case 0: window.track0.preset(name);
    }
}
</script>
</head>
<body>
<hr />
<input type="button" value="init" onclick="init(0)"></input>
<input type="button" value="init( iOS Simulate )" onclick="init(1)"></input> |  
<hr />Track0:
<input type="button" value="BGM0 (MUTE)" onclick="preset('BGM0', 0)"></input>
<input type="button" value="BGM1" onclick="preset('BGM1', 0)"></input>
<input type="button" value="BGM2" onclick="preset('BGM2', 0)"></input> |
<input type="button" value="pause" onclick="track0.pause()"></input>
<input type="button" class="pconly" value="mute" onclick="track0.mute()"></input> <!-- iOS not work -->
<input type="button" class="pconly" value="vol+0.1" onclick="track0.volume(0.1, '+')"></input> <!-- iOS not work -->
<input type="button" class="pconly" value="vol-0.1" onclick="track0.volume(0.1, '-')"></input> <!-- iOS not work -->
<input type="button" value="seek+10" onclick="track0.seek(10, '+')"></input>
<input type="button" value="seek-10" onclick="track0.seek(10, '-')"></input>

<hr />Track1:
<input type="button" value="SE_001(レベルアップ)" onclick="preset('SE_001', 1)"></input>
<input type="button" value="SE_002(技発動)" onclick="preset('SE_002', 1)"></input>
<input type="button" value="SE_003(打撃音)" onclick="preset('SE_003', 1)"></input>
<input type="button" value="SE_004(勝利)" onclick="preset('SE_004', 1)"></input>
<input type="button" value="SE_005(敗北)" onclick="preset('SE_005', 1)"></input>
<input type="button" value="SE_006(決定ボタン)" onclick="preset('SE_006', 1)"></input>
<hr />Track2:
<input type="button" value="Z_before_boss(ボス戦前)" onclick="preset('Z_before_boss', 2)"></input>
<input type="button" value="Z_after_boss(ボス戦後)" onclick="preset('Z_after_boss', 2)"></input>
<input type="button" value="Z_gacha(ガチャ)" onclick="preset('Z_gacha', 2)"></input>


<div id="logger"></div>
</body>
</html>

資料

Mobile Safariに関する制限

Mobile Safari(iPhoneのブラウザ)の制限と仕様です。

  • 最大同時発音数が1。本来であれば BGM を鳴らしながら SE を再生できません
    • 複数のオーディオファイルをロードできません。最後にロードされたファイルしか再生できません
  • プログラムから volume を制御できません。プログラムから mute もできません
    • ハードキーやリモコンから本体のボリューム操作は可能です
  • mp3 は再生できますが ogg は再生できません
  • <audio controls> が利用可能です
  • <audio autoplay> や preload は利用不能です
  • ユーザのタッチ操作を起点として(イベントハンドラ内で)、audio.play() や audio.load() を行う必要があります
    • タッチ操作を起点とせずにデータのロードや再生を行う事はできません。
    • これはパケット従量制のキャリアでiPhoneを利用するユーザがパケ死しないための規制です
Android Browserに関する制限

Android標準ブラウザの制限と仕様です。

  • 同時発音数は2以上(デバイス依存かも… 詳細不明)。BGMを鳴らしながらSEを再生できます
  • プログラムから volume は制御できません。プログラムから mute もできません
    • ハードキーからボリューム操作は可能です
  • mp3 は再生できますが ogg は再生できません
  • Mobile Safari と異なり、ユーザのタッチイベントを起点にしなくても音源のダウンロードと再生が可能です
Mobile Firefox に関する制限

Android上で動作するMobile Firefox(Fennec)の制限と仕様です。

  • 最大同時発音数は2以上。BGMを鳴らしながらSEを再生できます
  • プログラムから volume を制御できます。 プログラムから mute もできます
  • ogg は再生できますが mp3 は再生できません
    • ogg の再生でノイズが乗り正常に再生不能です
      • vorbis公式サイトのファイルでも音は鳴るが、2~3秒でしゃっくりしてしまいます
  • <audio controls> が挙動不審です。再生開始は可能ですが、その後コントロールできない状態になります
Opera Mobile に関する制限

Android上で動作するOpera Mobileの制限と仕様です。

  • audio.addEventListener でイベントハンドラがコールバックされません
    • コールバックされないため、まともにロジックが組めません。再生するぐらいしかできません
仕様/制限一覧
モバイル
ブラウザ
OS 同時
発音数
BGM+SE
同時再生
mp3 ogg Mute Volume イイね
Safari 4.0+ 1 △ (擬似) × × × ☆☆☆
Android 2.3+ 2+ × × × ☆☆☆
Fennec - 2+ ×
Opera - 2+ × × -

ゴニョゴニョ

  • 先頭の15秒の無音時間には2つの理由があります
    • iOSの制限を回避しつつコードをシンプルにするため、audio.load() ではなく audio.play() でデータのロードを開始しています。その際に先走って音がならないように無音時間が必要になります
    • 先頭の0~10秒を使って、BGM を OFF にできます(実際には無音のBGMが再生されつづけていますが)
  • 再生が終わってしまうと、次回の再生開始や頭出しに時間がかかることがあるため、常に再生し続けるようにしています
  • 再生開始タイミングと再生終了タイミングは iPhone で実際に聞いて耳で合わせてください
    • Android は再生時間が短い(0.1~0.5秒)と再生されたりされなかったりと不安定になるため、内部で再生時間を延長する等の補正を行なっています
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy