aoirint's note

メモ帳(古い記事はkanomiya blogから移行)

VBA-M on Docker

VisualBoyAdvance - MをDocker上で動かすDockerfile(とrunコマンドのオプションセット)を作った。 要X Window System、Pulseaudio。Ubuntu Desktop 18.04(with NVIDIA Driver)で動作確認。

Dockerfile

# for general machine
FROM ubuntu:bionic

# for nvidia-driver machine
# FROM nvidia/opengl:base-ubuntu18.04

ENV VERSION 2.1.4
ENV SHA1HASH bf6e452b53f47e2fbc4e6e41c92f567aa285cdbe

WORKDIR /vbam

RUN apt update \
  && apt -qq -y --no-install-recommends install \
  ca-certificates \
  tar \
  wget \
# -- from builddeps script
  build-essential \
  g++ \
  nasm \
  cmake \
  ccache \
  gettext \
  zlib1g-dev \
  libgl1-mesa-dev \
  libavcodec-dev \
  libavformat-dev \
  libswscale-dev \
  libavutil-dev \
  libswresample-dev \
  libgettextpo-dev \
  libpng-dev \
  libsdl2-dev \
  libsdl2-2.0 \
  libglu1-mesa-dev \
  libglu1-mesa \
  libgles2-mesa-dev \
  libsfml-dev \
  libsfml-graphics2.4 \
  libsfml-network2.4 \
  libsfml-window2.4 \
  libglew2.0 \
  libopenal-dev \
  libwxgtk3.0-dev \
  libwxgtk3.0 \
  libgtk2.0-dev \
  libgtk-3-dev \
  zip \
# sound driver to play sound on host
  pulseaudio \
# build
  && mkdir /vbam-build && cd /vbam-build \
  && wget -O vbam.tar.gz https://github.com/visualboyadvance-m/visualboyadvance-m/archive/v${VERSION}.tar.gz \
  && echo "${SHA1HASH} vbam.tar.gz" | sha1sum -c - \
  && mkdir src \
  && tar xf vbam.tar.gz -C src --strip-components 1 \
  && mkdir build && cd build \
  && cmake ../src \
  && make \
# copy to /usr/local/bin/
  && mv visualboyadvance-m /usr/local/bin/ \
# remove build environment
  && rm -r /vbam-build/

ベースイメージは(とりあえず)基本はubuntu:bionicで、NVIDIAGPUで動いてるマシンでビルドするときはnvidia/openglにする。これをやらないと描画時にlibGL error: No matching fbConfigs or visuals foundを吐く(逆にubuntu:bionicでの動作確認はしていないが.. 設定で描画をOpenGL以外にすればどちらでも動きそう)。

頭の環境変数を適切なものに変えてバージョンを切り替える。

ca-certificateswgetGitHubからリリースを持ってくるときの証明書周りのエラー対策。tarwgetは持ってきて解凍する用。 build-essentialからzipまでは同梱されてる./builddeps(v2.1.4)を実行したときに呼び出されたコマンドから持ってきていて、 pulseaudioは音声再生用。

#buildから先がビルド用のコマンドで、makeの実行が終わった段階でvisualboyadvance-m(実行ファイル)が生成される。 これをPATHの通った/usr/local/binに移動して、(たぶん)いらないビルド環境を削除する。 ほんとはcmake、make、g++とかだけ入ったビルド用のイメージ上でビルドしたあと、実行ファイルだけ実行用のイメージにコピーしたほうがいいかもしれない。 依存関係が多くて(長くなって)面倒そうだったのでやってない。

docker run

xhost + local:root

ホスト上でローカルのrootユーザに対してX Serverのアクセス制限を解除。 これでDockerコンテナに共有する/tmp/.X11-unixを介してDockerコンテナ上のrootユーザがX Clientを実行できるようになる。 rootだったらいい気がするが、一応全部のアクセス制限を復活させるときはxhost -

sudo docker run -it --rm --name vbam \
  -e DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  --gpus all \
  --group-add $(getent group audio | cut -d: -f3) \
  -e PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native \
  -v ${XDG_RUNTIME_DIR}/pulse/native/:${XDG_RUNTIME_DIR}/pulse/native \
  -v ${HOME}/.config/pulse/cookie:/root/.config/pulse/cookie \
  -v ${PWD}/vbam:/vbam \
  -v ${PWD}/vbam-conf:/root/.config/visualboyadvance-m \
  vbam \
  visualboyadvance-m

DISPLAY/tmp/.X11-unixX Window用、 --gpus allは一応nvidia/openglをベースイメージにしたときのGPU指定を入れている。

group-addから${HOME}.config/pulse/cookieをマウントしてるところまでが音声再生用。ここは OpenSiv3D を Docker 上で動かす - nekketsu^ω を参考にした。ホスト上で鳴らしたサウンドと同じようにpavucontrolから見える。

ホストのvbamディレクトリをマウントしてVBA-Mからホスト側のファイルを参照できるようにする。 このディレクトリから読み込んで実行すれば.savファイルは同じディレクトリに保存されるのでホスト側に永続化される (vbamディレクトリ以下にセーブファイルディレクトリを設定すれば同じく永続化される)。

それから、vbam-confディレクトリをマウントしてコンフィグをホスト側に永続化するようにする。

Jekyll Blogging お試し

Ruby製の静的ウェブサイト生成ツール(Static Site Generator)。なんかMarkdownとかで書いたサイトをいい感じにHTMLにしてくれるやつ。

Dockerイメージの準備

Ruby, RubyGems, gcc, makeが入っていれば動くらしい。公式Dockerイメージもあるみたいだけど、あえてスルーしてrubyイメージからやってみる。

ふだんRubyは使わないので試行錯誤。

まずはイメージをビルド。

FROM ruby:2

WORKDIR /code

RUN gem install jekyll bundler

ここでgem installしてもいいのか、という問題がありそうだけどよくわからない..(キャッシュについてはいいとして)

sudo docker build . -t myjekyll

ひとまずこれでイメージの準備はできた。

sudo docker run --rm -v `pwd`/myblog:/code -e BUNDLE_PATH=vendor/bundle myjekyll jekyll new ./

これで./myblog/code)に新しいJekyllプロジェクトが生成される(合わせて依存ライブラリがmyblog/vendor/bundleにインストールされる)。

vendorを除いたフォルダ構成はこんな感じ。

myblog/
├── 404.html
├── about.markdown
├── _config.yml
├── Gemfile
├── Gemfile.lock
├── index.markdown
└── _posts
    └── 2020-05-31-welcome-to-jekyll.markdown

もし既存のプロジェクトを使う場合、bundle installでライブラリを取得する(Gemfileに書かれた依存ライブラリがmyblog/vendor/bundleにインストールされる)。

sudo docker run --rm -v `pwd`/myblog:/code -e BUNDLE_PATH=vendor/bundle myjekyll bundle install

最後に開発用サーバを立てる。

sudo docker run --rm -v `pwd`/myblog:/code -e BUNDLE_PATH=vendor/bundle -p 4000:4000 myjekyll bundle exec jekyll serve -H 0.0.0.0 -P 4000

http://localhost:4000でチェック。

ディレクトリ構成

./

Gemfileなどがあるディレクトリ。ここはテンプレート(テーマ)置き場に使うみたい。

デフォルトでindex.markdownファイルはこうなっていた。

---
# Feel free to add content and custom Front Matter to this file.
# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults

layout: home
---

_posts/

記事のMarkdownファイルをおく場所。

サンプル記事2020-05-31-welcome-to-jekyll.markdownの冒頭(適当に改行を入れた)はこうなっていた。

---
layout: post
title:  "Welcome to Jekyll!"
date:   2020-05-31 13:16:10 +0000
categories: jekyll update
---
You’ll find this post in your `_posts` directory. Go ahead
and edit it and re-build the site to see your changes. You
can rebuild the site in many different ways, but the most
common way is to run `jekyll serve`, which launches a web
server and auto-regenerates your site when a file is updated.

ヘッダ部分(最初の---で囲まれた場所)をFront Matterというらしい(YAMLフォーマット)。ここにメタデータを書く。

_site/

ビルドされた静的サイトを構成するファイル群が出力される場所。

基本的なコマンド

jekyll build

Jekyllが生成した静的サイトを構成するファイルは_siteディレクトリ以下に出力される。自分の作成したMarkdown記事からHTMLを生成するときはjekyll buildを実行する。

jekyll serve

開発用サーバを立ち上げるコマンド。生成された静的サイトを確認するときはjekyll serveを実行する。デバッガ(プレビュー)的な機能があるようで、変更が自動的に反映される(自動的にjekyll buildしてくれる)らしい。記事以外を編集した場合は反映されないみたい?

記事(Post)の作成

_postsディレクトリ内に.md/.markdownファイルを増やせばよい。サンプル記事に書いてあるのだが、ファイル名はYEAR-MONTH-DAY-title.MARKUPにする必要があるらしい。

2020-05-31-my-first-post.md

---
layout: post
title:  "My First Post!"
date:   2020-05-31 22:05:00 +0900
tag:    First Post
tag:    Greeting
---
Hello Jekyll!

- [Jekyll](https://jekyllrb.com/)

テーマの変更(例:Minimal Mistakes)

Jekyll 3.2からgemでデフォルトのテーマを導入するようになったらしい。デフォルトはminima。 そのため_layouts, _includesなどのテーマ編集用のファイルが新規プロジェクトに生成されなくなった。

試しにテーマをMinimal Mistakesに変更してみる。

まずGemfileを編集し、以下の行を追加する。

# https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/
gem "minimal-mistakes-jekyll"

次に_config.ymlthemeを変更するのだが、他にも色々とテーマに依存した設定をこのファイルに書くようなのでGitHubから持ってきて置き換えた方がいい気がする。

# theme: minima
theme: minimal-mistakes-jekyll

デフォルトのindex.markdownをMinimal Mistakesのindex.htmlに置き換える(index.markdownを削除、index.htmlをダウンロードして置き換えなど)

投稿のlayoutpostからsingleになっているので各Markdownファイルを修正する(about.markdownも忘れずに、あるいは削除)。

それから、タグ検索ページを追加する。_pages/tag-archive.mdを下からコピーしてくればOK(_config.ymlリポジトリのものに置き換えたと想定)。

これでbuildすればテーマが変わる。細かい作業が多くて結構面倒くさい..。Git管理することを考えて、できる限りもとのファイルを維持したまま置き換えようとしたからかな? 諦めて_postsだけを移行するようにして、丸ごと入れ替えてしまったほうが楽だったかもしれない。なんか昔のHTMLテンプレートを記事だけ使いまわせるようにした、みたいな...。

コマンドで新規記事をテンプレートから作成できそうなのも見かけたし、記事を書くときにも色々追加記法があって、それからテーマの作成についても書こうかと思っていたけど、また今度。

User Style Sheet(Firefox)


Firefox 69以降デフォルトで無効化されたらしいので、about:configからstylesheetsで検索、toolkit.legacyUserProfileCustomizations.stylesheetstrueにする。

ブラウザ右上の三本線メニューからHelp、Troubleshooting InformationのProfile Directoryという項目から現在のプロファイルのディレクトリがわかる。

PROFILE_DIR/chromeディレクトリを作成、PROFILE_DIR/chrome/userContent.cssファイルを作成。あとはこのCSSがすべてのWebサイトに適用されるのでCSSを書いていくだけ。Firefoxを再起動すると変更が適用される。

body { background: black !important; }

@-moz-documentで特定のドメインのWebサイトにだけスタイルを適用できる(@documentでは動かなかった)。ドキュメントは@documentのものを見ればいいのかな(Firefoxにしかこのクエリは実装されてないらしい、CSS 4で検討中?)。ID/クラスが被ってる場合に使えるか。

@-moz-document domain(twitter.com) {
  body { background: black !important; }
}

Ubuntu上のデスクトップ音声出力を音声入力として与える(PulseAudio)

Environment

スピーカー出力をマイク入力として与える

pavucontrolを実行し、Input Devicesのタブを開く。Show:All Input Devicesに設定すると、Monitor of YOUR_SPEAKERという項目があり、このデバイスからの読み取りはスピーカーへの出力がループバックされたものになっている(YOUR_SPEAKERへの出力=Monitor of YOUR_SPEAKERからの入力。

しかしここで音声を入力するアプリケーションとして仮にZoomを開いても、Microphoneの選択肢にMonitor of YOUR_SPEAKERは現れない。回避策として、MicrophoneをSame as System(デフォルトの音声入力デバイス)にし、Input DevicesでMonitor of YOUR_SPEAKERSet as Fallback(緑のチェックマーク、デフォルトの音声入力デバイス)を有効にすることでZoomのマイクとしてスピーカー出力を渡すことができた。

このままでは他の人から送られてきた音声が自分のコンピュータの中でループ(Zoom Output→YOUR_SPEAKER→Monitor of YOUR_SPEAKER→Zoom Input)してしまうんじゃないかと思うが、少なくともZoom上ではそうはならなさそう(PC1のマイク→Zoom→PC2の音声出力→Monitor of PC2 Speaker→Zoom→PC1の音声出力 とはならなかった?)? これはハウリング防止フィルタが効いてるのかどうなのか..

Zoomの音域系フィルタが効いているのか音楽は若干変になる(もともと声を送るためのチャンネルだし。きれいな音声を送るにはShare Screenを使う必要ありか)。

Zoomの入力デバイスをpavucontrolから直接変えられないのはZoom側でなにか固定してるのか、チャンネル数とかフォーマット対応してないみたいなことなのか..

前回のデスクトップ映像の出力と組み合わせた場合、映像と音声の同期がとれないと思われるので、お気持ちでなんとかするか、あるいはffmpegで同時に送り出すことまでを保証する、くらいはできるのだろうか..

挿入音声の再生デバイス分離

Zoomの音声を出力するデバイスと挿入音声を出力するデバイスを分離して、目的の音声だけ送り出せるようにしてみる。つまり、Zoomの音声出力はYOUR_SPEAKERのままにして、挿入音声を仮想音声出力デバイスに出力するようにする。

pacmd load-module module-null-sink sink_name=DummyOutput0 sink_properties=device.description=DummyOutput0
# Or
pacmd load-module module-null-sink sink_name=DummyOutput0
pacmd update-sink-proplist DummyOutput0 device.description=DummyOutput0
# ---

pacmd unload-module module-null-sink

これでデフォルトの音声入力デバイスDummyOutput0に設定すればZoom上にDummyOutput0への出力が送り出される。あとは挿入音声を流しているアプリケーションからの音声出力をDummyOutput0に送り出せばOK。

この状態でDummyOutput0に出力されている挿入音声をYOUR_SPEAKERで聞くには、Monitor of DummyOutput0をループバックしてYOUR_SPEAKERに出力する。

pacmd load-module module-loopback source=DummyOutput0.monitor

pacmd unload-module module-loopback

物理マイクの入力をミックスする

ここでZoomの入力デバイスに送り出されるのは自分のコンピュータ上で出力された音声だけになっているので、物理マイクを接続していてもこれに入力された音声をZoomに送り出すことはできていない。物理マイクへの入力もZoom上に送り出せるようにする。

まず、物理マイクのPulseaudio上でのデバイス名を調べる。

pactl list short sources
pactl list sources

いずれかのコマンドで自分のマイクのデバイス名(alsa_input.*)、もしくはID(数値)がわかるはず。これをループバックする仮想デバイスを作成する。

pacmd load-module module-loopback source=YOUR_MIC_NAME_OR_ID
# Or
pacmd load-module module-loopback source=YOUR_MIC_NAME_OR_ID sink=DummyOutput0

pacmd unload-module module-loopback

音声の出力先(sink)はpavucontrol上で変更できる。

Pulseaudioを再起動する

実験中に変な設定をしてしまったのかPulseaudioが応答しなくなってしまうことがあったので、再起動手順を書いておく。

まず、通常の手順と思われるもの。

pulseaudio --kill
pulseaudio --start

次に、強制的にプロセスキルして再起動するもの。

ps -e | grep pulseaudio
kill -9 PID

pulseaudio --start
# Or
pulseaudio -D # Daemon startup failed?

参考

ボツコマンド集

# 入力FIFOデバイスの作成、これに出力を与える方法がわからない
pacmd load-module module-pipe-source file=/tmp/DummyInput0.input source_name=DummyInput0 source_properties=device.description=DummyInput0

pacmd unload-module module-pipe-source
# 出力FIFOデバイスの作成、メモ
pacmd load-module module-pipe-sink file=/tmp/DummyOutput1.output sink_name=DummyOutput1 sink_properties=device.description=DummyOutput1

pacmd unload-module module-pipe-sink
# 出力先がRead-onlyなのでむり
ffmpeg -f pulse -i DummyOutput0.monitor -f pulse DummyInput
# 1回違う設定をしてからアンロードすると設定できる謎コマンド
pactl load-module module-echo-cancel sink_master=DummyOutput0
pactl unload-module module-echo-cancel
pactl load-module module-echo-cancel source_master=DummyOutput0.monitor

pactl unload-module module-echo-cancel

Ubuntu上のOBSでVirtualCamを使う(デスクトップ映像を仮想カメラに送る)

Zoomなどでデスクトップ画面をカメラ映像として共有するのに使える。

Environment

  • Ubuntu 18.04
  • FFmpeg 3.4.6-0ubuntu0.18.04.1
  • v4l2loopback #ed2b709
  • OBS Studio 25.0.8 #14b0565

obs-v4l2sink #1ec3c8a はOBS Studioがクラッシュして使えなかった。

How to

# sudo apt install v4l2loopback-dkms # これはバージョンの問題で使えないかも

git clone https://github.com/umlaeute/v4l2loopback.git
cd v4l2loopback
make
sudo make install
sudo depmod -a
# 仮想カメラを作成(モジュールをロード)
sudo modprobe v4l2loopback devices=1 video_nr=10 card_label="OBS Cam"

# 映像転送サーバを立てる(OBS→仮想カメラ)
ffmpeg -an -listen 1 -i rtmp://127.0.0.1:1935/live -f v4l2 /dev/video10

# 仮想カメラを削除
sudo modprobe -r v4l2loopback

カメラデバイスの一覧を確認するにはsudo apt install v4l-utilsして以下のコマンド。

v4l2-ctl --list-devices

OBS Studioを開いてStreamingの設定をする。SettingsからStreamを選択。

一応、解像度を落とす設定をしたけどいらないかもしれない。Outputを開く。

  • Output Mode: Advanced
  • Rescale Output: 1280x720

Encoderはなにかかけてもffmpegが自動で認識して(rawvideoに)変換してくれそうだけど、ローカルでエンコードする必要ないはずなのではじめからrawvideoでStreamingしたいけど、選択肢がなさそう..。しかたないのでh264で転送した。

# ffmpegのログ

Stream mapping:
  Stream #0:1 -> #0:0 (h264 (native) -> rawvideo (native))

rawvideoの指定できるRecordingでも同じように設定できた(Recording to URLのURLにrtmpサーバを指定)けれど、Start RecordingするとOBS Studioがクラッシュしてしまった。

あとはOBS上でStart Streamingすれば仮想カメラへの配信が始まる。Stop Streamingするとffmpegが終了してしまうのだけれど、終了しないように設定できるのかな?

付録

一部のアプリケーション用の設定

ChromeやWebRTCで仮想カメラデバイスを使おうとすると問題が起こる(認識しない?)ことがあるみたい。Cheeseでも同じ。 これを回避するには仮想カメラの設定としてexclusive_capsを指定する必要がある。 OutputされていないときにOutputフラグを消す、な感じみたいだけど、他のアプリケーションが映像を読み出してると思って使えないデバイス扱いしちゃうのかな? Zoomの場合は逆にexclusive_capsを指定するとStart Videoできなくなった。

sudo modprobe v4l2loopback devices=1 video_nr=10 card_label="OBS Cam" exclusive_caps=1

OBSを使わない方法

デスクトップ画面全体をffmpegで仮想カメラデバイスに送る場合のメモ。以下のコマンドが使える( https://github.com/CatxFish/obs-v4l2sink/issues/5 )。デスクトップ番号が0でない場合は-i :0を書き換える。

ffmpeg -f x11grab -r 15 -s 1920x1080 -i :0 -vcodec rawvideo -pix_fmt yuv420p -threads 0 -f v4l2 /dev/video0

ボツ:obs-v4l2sinkを導入

最終的には動作しなかったが、メモ。

sudo apt install qtbase5-dev
git clone --recursive https://github.com/obsproject/obs-studio.git
git clone https://github.com/CatxFish/obs-v4l2sink.git
cd obs-v4l2sink
mkdir build
cd build
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install

obs-studioとobs-v4l2sinkのソースコードをクローンしてビルド・インストールしている。

インストール後にOBSを開くとメニューバーのToolsに V4L2 Video Output の項目が追加される。

Vue 入門

Node.js(npm+Browserifyなど)でパッケージ管理したり、サーバサイドで事前処理(.vueファイル、vue-cli)して配信ファイルを生成したりしてややこしいけれど、基本的にVue(Vue.js)はブラウザ上で動作するBootstrap++みたいなクライアントサイドフレームワークという理解。

参考

この「はじめに」をベースにサンプルを並べていく。

何もしないHTML

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<h2>Hello Vue!</h2>
<p>
  This is not Vue!

CDNから開発用のVue.jsを持ってくるだけ。これをベースにいじっていく(SRIは略)。

テストするときはpython3 -m http.server --bind 127.0.0.1とか、php -S 127.0.0.1:8000とか、静的コンテンツを開ければなんでもよし。

テンプレート的用法

適当に使ってみる

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<h2>Hello Vue!</h2>
<p id="body">
  {{ body_content }}

<script>
(function() {
  var app = new Vue({
    el: '#body',
    data: {
      body_content: 'This is BODY!',
    },
  });
})();
</script>

Django/Jinjaのように{{ variable }}の形式で埋め込み位置を指定。Vueオブジェクトを作って初期値を与える。

コンテナ要素で使ってみる

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <h2>{{ title }}</h2>
  <p>
    {{ body }}
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      title: 'Hello Vue!',
      body: 'This is BODY!',
    },
  });
})();
</script>

要素の内部に複数の埋め込みを行う。

属性をテンプレート化

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <h2 v-bind:title="tooltip">{{ title }}</h2>
  <p>
    {{ body }}
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      tooltip: 'Hover text',
      title: 'Hello Vue!',
      body: 'This is BODY!',
    },
  });
})();
</script>

属性(この場合、ホバーテキスト/ツールチップを表すtitle属性)に埋め込みを行う(titleという名前が2つ出てきてややこしくなってしまった)。

スクリプトからの書き換え

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <h2 v-bind:title="tooltip">{{ title }}</h2>
  <p>
    {{ body }}
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      tooltip: 'Hover text',
      title: 'Hello Vue!',
      body: 'This is BODY!',
    },
  });

  app.title = 'This is TITLE!';
  app.tooltip = 'This is TOOLTIP!';

})();
</script>

埋め込みテキストはスクリプトから変更可能(すぐに反映される)。

イベントハンドリング

クリックイベント

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <h2 v-bind:title="tooltip">{{ title }}</h2>
  <p>
    {{ body }}
  <p>    
    <button v-on:click="onButtonClicked">Switch</button>
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      tooltip: 'Hover text',
      title: 'Hello Vue!',
      body: 'This is BODY!',
    },
    methods: {
      onButtonClicked: function() {
        this.title = 'This is TITLE!'; // this == app
        this.tooltip = 'This is TOOLTIP!';
      },
    },
  });

})();
</script>

クリックイベントが起きたとき埋め込みテキストを書き換える。

Vueの内部状態とinputタグ

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <p>
    <input v-bind:value="field1">
  <p>
    <input v-model="field2">
  <p>
    <button v-on:click="check">Check</button>
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      field1: 'Field 1',
      field2: 'Field 2',
    },
    methods: {
      check: function() {
        console.log(app.field1);
        console.log(app.field2);
      },
    }
  });

  app.check();

})();
</script>

Vueの内部状態と画面上の状態がずれたときの挙動を試す。v-bind:valueでは再描画が走ったときに値がVueの内部状態にリセットされる。これを防ぐ(Vueの内部状態と表示状態を同期する)にはv-modelを使う(双方向バインディング)。

スタイル操作

Visibility

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <p>
    <button v-on:click="onButtonClicked">Switch</button>
  <p v-if="message_visible">
    Hello Vue!
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      message_visible: true,
    },
    methods: {
      onButtonClicked: function() {
        app.message_visible = !app.message_visible;
      },
    }
  });

})();
</script>

display: noneの切り替え。条件分岐という扱いらしい。

<!-- Django / Jinja -->
{% if message_visible %}
<p>
  Hello Vue!
{% endif %}

これの代わりなのはわかるが、タグ名が先に来ているのが読みにくい..(Rubyか?)。

再利用

配列のマッピング

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<div id="app">
  <ol>
    <li v-for="item in items">
      {{ item.text }}
  </ol>
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      items: [
        { text: 'Item 1' },
        { text: 'Item 2' },
        { text: 'Item 3' },
      ],
    },
  });

})();
</script>

v-forを付けた要素をリストに基づいて複製して個数分表示する。これもタグ名が先に来ているのが読みにくい..。

<!-- Django / Jinja -->
<ol>
{% for item in items %}
  <li>
    {{ item.text }}
{% endfor %}
</ol>

これの代わりなのはわかる。

コンポーネント

新しいタグ名を定義する感覚で要素の再利用(コード上での定義)ができる。

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<script>
  Vue.component('my-item', {
    template: '<li>Item</li>',
  });
</script>

<div id="app">
  <ol>
    <my-item v-for="item in items"></my-item>
  </ol>
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      items: [
        { text: 'Item 1' },
        { text: 'Item 2' },
        { text: 'Item 3' },
      ],
    },
  });

})();
</script>

閉じタグ省略したら警告出た。textをちゃんと使うには、以下のようにする。

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script> -->

<script>
  Vue.component('my-item', {
    props: [ 'my_item', ],
    template: '<li>{{ my_item.text }}</li>',
  });
</script>

<div id="app">
  <ol>
    <my-item
      v-for="item in items"
      v-bind:my_item="item"
      ></my-item>
  </ol>
</div>

<script>
(function() {
  var app = new Vue({
    el: '#app',
    data: {
      items: [
        { text: 'Item 1' },
        { text: 'Item 2' },
        { text: 'Item 3' },
      ],
    },
  });

})();
</script>

v-bind:text="item.text"みたいなこともできるらしい。

Vue CLIって何

今回は単一のHTMLファイルに全部書いてるが、巨大なプロジェクトではそうもいかない。 コンポーネントの定義なんかは外部のJSを持ってくればよさそうだけれど、これも数が多くなると管理がつらくなってくる。 このへんをなんかいい感じ(コンポーネントごとにファイル分けたり)にして、ついでにURL(パス、ルーティング)もいい感じにできる(JSでルーティング設定)ようにして、最終的にブラウザ側でVueの処理をいい感じにできるように整えた静的サーバコンテンツ(GitHub Pagesとかで動くやつ)を生成するツールを作ってみた、というのがVue CLIっぽい。

Zoom API (OAuth)を試したメモ

お試しのためのOAuthアクセストークン(一時)を得るサンプルをクローン。

git clone https://github.com/zoom/zoom-oauth-sample-app.git
cd zoom-oauth-sample-app

npm install

https://github.com/zoom/zoom-oauth-sample-app

ngrok(remote.itみたいなやつ)をダウンロード(実行ファイル)。ngrok.ioのサブドメインにポートを転送する。

https://ngrok.com/

./ngrok http 4000

OAuth AppをZoom上に作成。

https://marketplace.zoom.us/develop/create

設定を書く.envファイルを作成。

touch .env
clientID=Zoom OAuth AppのClient ID
clientSecret=Zoom OAuth AppのClient Secret
redirectURL=ngrokのURL

起動。

npm run start

API Document: https://marketplace.zoom.us/docs/api-reference/introduction

上のURLでテスト呼び出しができるので、アクセストークンをとりあえず使いまわして検証。

管理者でない場合は得られる情報が結構限られる(進行中のミーティングの参加者リストは管理者でないととれなさそう)。管理者の場合はOAuthではなくJWTを使ったほうが良さそう。今回の目的は達成できなさそうだったのでこれで終了(トークンとらなくても入出力サンプルはある)。

JWT参考:インタラクション2020のリモート開催で使われたシステム https://github.com/hasevr/i2020zoom