いつもの通り、自分が悩んだ末にたどり着いた記録を残したいと思います。
日本語プログラミング言語「なでしこ」 Advent Calendar 2020に連続投下することになるのを,少しためらったのですが…。空気を読まずに参加したいと思います(ごめんなさい)。
この記事は、なでしこのv3ブラウザ版で実行できるようにしています。
v3ブラウザ版で音声ファイルを再生したい
以下に,自分が悩んだ経過をまとめていますが,完成したテストプログラムはここに置いておきます。
標準の命令
なでしこv3ブラウザ版では,オーディオ開く
等の命令が用意されています。
A=「https://nadesi.com/v3/common/sound/ki.mp3」をオーディオ開く。
Aをオーディオ再生。
ところが,なでしこv3簡易エディタでこれを実行すると,最初の1回だけ音が鳴りますが,2回目以降は音が鳴らないのです(私だけ?)。さらに,ローカルで音声ファイルを用意して試したい時に,file:///d:/xxx.wav
のようにローカルパスを指定できませんでした。
どうしてもローカルのファイルを再生したかった
Webサーバが無いと音声ファイルを扱えないということになると,学校などでローカルで学習するのが難しくなってしまいます。
そこで,どうしてもローカルのファイルを再生したかったので,簡易エディタをあきらめて,ローカルにHTMLファイルを作り,それになでしこのプログラムを組み込んでみることにしました。
ローカルで音声ファイルを再生する
フォルダとファイルの準備
まず,自分のPC上(WindwosでもChromebookでも同じです)に,プログラムを保存するためのフォルダを作ります。
その中に,次のファイルを入れておきます。
- なでしこのプログラムを実行するためのHTMLファイル
- 再生したい音声ファイル
- 今回作ったプラグインファイル plugin_media試作
なでしこ用HTMLファイルのひな形
自分でHTMLファイルを作って「なでしこ」を実行したかったので,次のようにひな形を作りました。
<body>
要素の前半では,画面レイアウトをあらかじめhtmlで作っておきます。このサンプルでは,audio要素をあらかじめ設定していますが,このようにあらかじめaudio要素を埋め込んでおいてもいいし,後で動的に生成してもいいです。
<body>
要素の後半では,なでしこ3のエンジンを読み込む処理と,今回作ったプラグインplugin_media
(試作)を読み込む処理があります。
//初期設定
にある2行は,必ず実行します。その続きから,なでしこのプログラムを書いていきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>なでしこ 音読込のテスト</title>
</head>
<body>
<!-- 画面レイアウトをあらかじめ作っておく -->
<h1>なでしこ3ブラウザ版で音を扱うテスト</h1>
<audio id="audio1"></audio>
<hr />
<div id="div1"></div>
<!-- なでしこ3のエンジンを取り込み -->
<script type="text/javascript" src="https://nadesi.com/v3/cdn.php?v=3.1.8&f=release/wnako3.js&run"></script>
<!-- プラグインを取り込み -->
<script type="text/javascript" src="plugin_media.js"></script>
<!-- なでしこのプログラム -->
<script type="なでしこ">
// 初期設定 -- HTML内でなでしこを使うときは必ず実行する
結果領域=「#div1」。
結果領域へDOM親要素設定。結果領域に「」をHTML設定。
// ここから,なでしこのプログラムを書いていきます
</script>
</body></html>
音を扱うプログラム(HTML+なでしこ)
テンプレートを使って,音を扱う命令を組み込んでみます。
今回作った命令は音読込
,音再生
,音停止
,音再開
の4つです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>なでしこ 音読込のテスト</title>
</head>
<body>
<!-- 画面レイアウトをあらかじめ作っておく -->
<h1>なでしこ3ブラウザ版で音を扱うテスト</h1>
<audio id="audio1"></audio>
<hr />
<div id="div1"></div>
<!-- なでしこ3のエンジンを取り込み -->
<script type="text/javascript" src="https://nadesi.com/v3/cdn.php?v=3.1.8&f=release/wnako3.js&run"></script>
<!-- プラグインを取り込み -->
<script type="text/javascript" src="plugin_media.js"></script>
<!-- なでしこのプログラム -->
<script type="なでしこ">
// 初期設定 -- HTML内でなでしこを使うときは必ず実行する
結果領域=「#div1」。
結果領域へDOM親要素設定。結果領域に「」をHTML設定。
// ここから,なでしこのプログラムを書いていきます
// ----- あらかじめ埋め込んだaudio要素を使う方法
// audio要素に音声ファイルを指定しておく
「audio1」に「bgm1.mp3」を音読込。 // #1
// コントロールボタンの設置
ボタン1=「再生」のボタン作成。
ボタン1をクリックした時には
「audio1」を音再生。 // #2
ここまで。
ボタン2=「停止」のボタン作成。
ボタン2をクリックした時には
「audio1」を音停止。 // #3
ここまで。
ボタン3=「再開」のボタン作成。
ボタン3をクリックした時には
「audio1」を音再開。 // #4
ここまで。
</script>
</body></html>
#1では,<id名>に<ファイル名>を音読込
と書いています。あらかじめ組み込んでいるid=audio1
のaudio要素に,ファイル名を設定しています。ローカルで作っている場合,ファイル名にはfile:///d:/xxx.wav
形式も使えます(やったぜ!)。
#2,#3,#4では,指定したIDのaudio要素を操作しています。音再生
では音声の開始位置を冒頭(0)に戻しています。音再開
では,開始位置を戻さずに再生しています。
plugin_media(試作)
そして,これらの命令を作ろうと思い,plugin_media.js
を作成しました。音以外の命令も作ったので,一部だけ抜き出して載せます。
// audio1.htmlに関係する部分のみ抜き出しています。
const PluginMedia = {
// --- 音関係 ---
'音読込': { // @id=aIDのaudio要素にaSrcファイルを読み込む // @オトヨミコミ
// あらかじめaudio要素を設置しておく場合はこっち。
type: 'func',
josi: [['に'],['を']],
fn: function (aID, aSrc, sys) {
try {
const audio = document.querySelector("#" + aID);
audio.src = aSrc;
} catch(e) {
// エラーを表示
window.alert('音読込 ' + e.message);
return -1;
}
}
},
'音再生': { // @id=aIDのaudio要素に設定されている音を頭から再生する // @オトサイセイ
type: 'func',
josi: [['を']],
fn: function (aID, sys) {
try {
const audio = document.querySelector("#" + aID);
audio.currentTime = 0;
audio.play();
} catch(e) {
// エラーを表示
window.alert('音再生 ' + e.message);
return -1;
}
}
},
'音再開': { // @id=aIDのaudio要素に設定されている音を停止位置から再生する // @オトサイカイ
type: 'func',
josi: [['を']],
fn: function (aID, sys) {
try {
const audio = document.querySelector("#" + aID);
audio.play();
} catch(e) {
// エラーを表示
window.alert('音再開 ' + e.message);
return -1;
}
}
},
'音停止': { // @id=aIDのaudio要素に設定されている音を一時停止する // @オトテイシ
type: 'func',
josi: [['を']],
fn: function (aID, sys) {
try {
const audio = document.querySelector("#" + aID);
audio.pause();
} catch(e) {
// エラーを表示
window.alert('音停止 ' + e.message);
return -1;
}
}
}
}
// モジュールのエクスポート(必ず必要)
if (typeof module !== 'undefined' && module.exports) {
module.exports = PluginMedia
}
//プラグインの自動登録
if (typeof (navigator) === 'object') {
navigator.nako3.addPluginObject('PluginMedia', PluginMedia)
}
特に難しいことをやっているわけではありませんが,Javascriptの命令をそれぞれ呼び出してみました。うまく動作しているので,これで大丈夫かと。
迷ったことは,冒頭にあるconst PluginMedia
と,末尾にあるmodule.exports = PluginMedia
,navigator.nako3.addPluginObject('PluginMedia', PluginMedia)
とで,PluginMedia
という名前を全て揃えないと,正しく実行されないということです。
また,それぞれの命令に例外処理用にtry ~ catch(e) ~
を入れています。HTMLにPluginを組み込んで試していると,エラーが起こった時にconsoleを見ないと間違った箇所が分からなくて困ったので,アラートを表示するようにしています。不要ならば削除してもいいと思います。
HTMLファイルをブラウザで表示してみる
ここまで作って,HTMLファイルをブラウザで開くと,画面にボタンが表示されます。そのボタンを押すと,音声ファイルを操作できます。
動的にaudio要素を生成する
先ほどのプラグインに音追加
という命令を加えます。こちらは,audio要素を新たに生成するための命令なので,HTMLファイルにaudio要素を組み込んでいなくても使えるようにしています。
// 先ほどのplugin_media-audio1.jsの中に追加します。
const PluginMedia = {
// 途中省略
'音追加': { // @audio要素を追加して,aSrcファイルを読み込む // @オトツイカ
// aIDを指定するとそれを親要素とする。省略するとbodyの子要素として追加。
// 生成されたid名を返します。
type: 'func',
josi: [['を'],['へ', 'に']],
isVariableJosi: true,
return_none: true,
fn: function (aSrc, ...pID) {
try {
const sys = pID.pop();
var parent = document.body;
if ( pID.length > 0 ) {
parent = document.querySelector("#" + pID[0]);
};
const audio = document.createElement('audio');
audio.src = aSrc;
audio.id = 'nadesi-dom-' + sys.__v0['DOM生成個数'];
parent.appendChild(audio);
sys.__v0['DOM生成個数']++;
return audio.id;
} catch(e) {
// エラーを表示
window.alert('音追加 ' + e.message);
return -1;
}
}
},
// 以降省略
}
なでしこ側では,<親id>へ<ファイル名>を音追加
と書きます。親IDの子要素としてaudio要素を追加し,指定したファイルを読み込みます。
親idを省略して<ファイル名>を音追加
と書いた場合は,なでしこ側で「DOM親要素設定」しているidの子要素として追加されます。
引数を省略できるようにする
- 引数を省略できるようにしたかったので,
josi:
の値は<親id>へ
の助詞を,一番最後にしています。ソースを見ると分かりますが,実は<親id>に
と書いても動作します。またisVariableJosi: true
が必要です(これを書いていなかったのでずっとハマった…)。 - 次の
fn: function (aSrc, ...pID)
の部分は,1つ目の引数aSrc
(ファイル名を指定する引数)は省略できないので,変数を指定します。次の...pID
は省略できるのでこのようにしています。JavaScriptではこれをレストパラメータと言うそうです。 - ここでは,pID[ ]という配列に値が入っており,その末尾はシステム変数
sys
になっているので,自作関数の冒頭でconst sys = pID.pop();
を入れて,システム変数を取り出しています。 - すると,引数があった場合は配列
pID[ ]
からsys
を取り出しても,まだ要素が残っているはずなので,次のif文ではpID.length
を使ってpID[ ]
の配列の要素数を数えます。pID.length > 0
ならば,引数があった(つまり親idをプログラムから指定した)ということなので,変数parent
にそれを設定する,ということです。 - 最後に
return audio.id;
とすることで,生成されたaudio要素のidを戻り値として取得できます。そのためにreturn_none: true
が必要です(これも書いてなかったのでハマった…)。
「なでしこ3」のプラグインは,引数を省略可能にしようと思ったら手強かったです…(JavaScriptに長けている方なら,カンタンだと思います)。でも上手くいったからよかった。
HTMLファイルを修正する
この命令をテストしたかったので,HTMLファイルを次のように修正します。(なでしこの部分だけ抜き出しています)
// ここから,なでしこのプログラムを書いていきます
// ----- 引数を省略して生成させる方法
ID=「bgm1.mp3」を音追加。
// コントロールボタンの設置
ボタン1=「再生」のボタン作成。
ボタン1をクリックした時には
IDを音再生。
ここまで。
ボタン2=「停止」のボタン作成。
ボタン2をクリックした時には
IDを音停止。
ここまで。
ボタン3=「再開」のボタン作成。
ボタン3をクリックした時には
IDを音再開。
ここまで。
改行作成。
// ----- 親要素idを指定する方法
ID2=「div1」に「poka.wav」を音追加。
// コントロールボタンの設置
ボタン4=「再生2」のボタン作成。
ボタン4をクリックした時には
ID2を音再生。
ここまで。
HTMLファイルをブラウザで表示してみる
ここまで作って,HTMLファイルをブラウザで開くと,画面にボタンが表示されます。そのボタンを押すと,音声ファイルを操作できます。
最後に
ホントはやらなくてはいけない仕事が山のようにあるのですが,どうしてもプラグインを作りたくなってしまい,久々に夜更かししてしまいました。でも,なでしこv3ブラウザ版で,ChromeBookでも音を扱えるようになったら楽しいですね! ということで,調子に乗って画像と動画も表示できるようにしたので,それは別の記事にします。
一応,音,画像,動画を扱えるようにした試作バージョンplugin_media.jsも置いておきます。間違っていたらアドバイスをいただけると嬉しいです。
参考
- v3マニュアル オーディオ
- なでしこv3 ユーザープラグイン
- プラグインAPIの仕様