1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

日本語プログラミング言語「なでしこ」Advent Calendar 2020

Day 15

なでしこv3ブラウザ版で音を扱う

Last updated at Posted at 2020-12-13

いつもの通り、自分が悩んだ末にたどり着いた記録を残したいと思います。
日本語プログラミング言語「なでしこ」 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試作

フォルダの例
pic1.PNG

なでしこ用HTMLファイルのひな形

自分でHTMLファイルを作って「なでしこ」を実行したかったので,次のようにひな形を作りました。

<body>要素の前半では,画面レイアウトをあらかじめhtmlで作っておきます。このサンプルでは,audio要素をあらかじめ設定していますが,このようにあらかじめaudio要素を埋め込んでおいてもいいし,後で動的に生成してもいいです。

<body>要素の後半では,なでしこ3のエンジンを読み込む処理と,今回作ったプラグインplugin_media(試作)を読み込む処理があります。
//初期設定にある2行は,必ず実行します。その続きから,なでしこのプログラムを書いていきます。

template.html
<!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つです。

audio1.html
<!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要素に音声ファイルを指定しておく
audio1bgm1.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を作成しました。音以外の命令も作ったので,一部だけ抜き出して載せます。

plugin_media-audio1.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 = PluginMedianavigator.nako3.addPluginObject('PluginMedia', PluginMedia)とで,PluginMediaという名前を全て揃えないと,正しく実行されないということです。

また,それぞれの命令に例外処理用にtry ~ catch(e) ~を入れています。HTMLにPluginを組み込んで試していると,エラーが起こった時にconsoleを見ないと間違った箇所が分からなくて困ったので,アラートを表示するようにしています。不要ならば削除してもいいと思います。

HTMLファイルをブラウザで表示してみる

ここまで作って,HTMLファイルをブラウザで開くと,画面にボタンが表示されます。そのボタンを押すと,音声ファイルを操作できます。

pic2.PNG

動的にaudio要素を生成する

先ほどのプラグインに音追加という命令を加えます。こちらは,audio要素を新たに生成するための命令なので,HTMLファイルにaudio要素を組み込んでいなくても使えるようにしています。

plugin_media-audio2.js
// 先ほどの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ファイルを次のように修正します。(なでしこの部分だけ抜き出しています)

audio2.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も置いておきます。間違っていたらアドバイスをいただけると嬉しいです。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?