DRYな備忘録

Don't Repeat Yourself.

【iPhone】【Safari】getUserMediaで取ったMediaStreamをvideoタグにセットしても最初のフレームだけ描画されて止まってしまう問題【React】

問題

  • iPhoneSafariにおいて、
  • navigator.mediaDevices.getUserMediaによって、
  • videoを含むMediaStreamを取得し、
  • HTMLのvideoタグに動画を描画しようとすると、
  • 最初のフレームだけ描画されたのち、フリーズしてしまう。

解決

getUserMediaを呼ぶ前に、videoタグにautoplay, muted, playsinline属性が付与されている必要がある。

stackoverflow.com

その他の注意

  • Reactのjsxないしtsxファイルにおいて、<video playsinline={true} />とした場合でも、最終的にレンダリングされたHTMLにその属性が付与されているか確認すること
    • されてなかったので、わざわざRefObjectつくってDOMのAPIからsetAttribute('playsinline', '')などとした
  • そもそもgetUserMediasecure context と呼ばれるウェブページにおいてのみ動作するため、HTTPSを使ったページにホストする必要がある

参考

雑感

  • 1日ハマった

DRYな備忘録として

iOSでFontAwesomeを使う【Xcode10.3】

Xcodeは変化が早いので日付に注意してください

ゴール

  • XcodeiOSプロジェクトでFontAwesomeが使える

参考

要素

  1. FontAwesomeのOTFファイルを入手する
  2. FontAwesomeのOTFファイルをXcodeプロジェクトに追加する
  3. 使ってみる

FontAwesomeのOTFファイルを入手する

fontawesome.com

今回欲しいのはOTFファイルなので、for DesktopのほうをDownloadします。

f:id:otiai10:20190808140812p:plain
ダウンロードして解凍したもの

FontAwesomeのOTFファイルをXcodeプロジェクトに追加する

ここに懇切丁寧に解説があるが、 Adding a Custom Font to Your App | Apple Developer Documentation

要素としては、

  1. 今後のためにfontsGroupをつくっておく
  2. ハードコピーを作成しつつotfファイルを追加する
  3. info.plistで、Fonts provided by applicationを追加する

f:id:otiai10:20190808141402p:plain
info.plist における Fonts provided by application の item の名前は、ファイル名から拡張子`.otf`を除いたものとした

使ってみる

f:id:otiai10:20190808142827p:plain

雑感

  • 意外とかんたんだった

DRYな備忘録として

【iOS】ボタン長押しの実装【Xcode10.3】【Swift5】

Xcodeは変化が早いので記事の日付に注意してください

ゴール

  • 長押ししてアラートとか出るボタンを設置する

tl;dr

override func viewDidLoad() {
    super.viewDidLoad()
    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLongPress))
    self.button.addGestureRecognizer(recognizer)
}

@objc func onLongPress() {
    let alert = UIAlertController(title: "Long Press", message: "親父にも長押しされたことないのに!", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

以下読まなくていいです。

1. ボタンの設置・追加

最近のXcodeはオブジェクトの追加をするペインが無い。Cmd + Shif + Lで、かつてオブジェクト選択のペインだったものがダイアログとして出てくる。

f:id:otiai10:20190808105440p:plain
Cmd + Shift + L

ここで、ButtonをStoryboardにドラッグアンドドロップして、適当にテキスト変えて、終了。

f:id:otiai10:20190808105756p:plain

2. 通常のTouchイベントの登録

Storyboardを表示した状態で、右上の「◯◯」を押すと、Storyboardに紐付いているViewControllerが反面に開かれる。

f:id:otiai10:20190808110042p:plain

で、ButtonとViewControllerを紐付ける。

f:id:otiai10:20190808110925p:plain

ちょっとAlert出すコード書く。

@IBAction func onTouchDownButton(_ sender: Any) {
    let alert = UIAlertController(title: "Touch Down", message: "押したねっ", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "close", style: .default, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

こうなる

f:id:otiai10:20190808112127g:plain

3. 長押しのRecognizerを設置

とりあえずさっき作って紐付けたTouch Downイベントは消して、以下のようなコードを書く。

このとき、self.buttonは、上記のような要領で、Ctrl押しながらUIパーツをViewControllerにひっぱってきて置いたもの。

override func viewDidLoad() {
    super.viewDidLoad()
    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLongPress))
    self.button.addGestureRecognizer(recognizer)
}

// func onLongPress(_ sender: UILongPressGestureRecognizer) {
@objc func onLongPress() {
    let alert = UIAlertController(title: "Long Press", message: "親父にも長押しされたことないのに!", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

こうなる

f:id:otiai10:20190808114936g:plain

雑感

  • Xcodeは補完がきもちいいですね
  • Swiftはguardが好きです
  • iOS開発たのしい

DRYな備忘録として

任意のURL(に限らず文字列)のQRコードをコマンドラインで生成する

という文言でググればいくらでも出てきますけど。

tl;dr

% pip install segno
% segno "https://before11.hatenablog.com/" --output myblog.png --scale 8

そしたらこういう画像が myblog.png として得られる

f:id:otiai10:20190625135536p:plain

おしまい

ログ

% pip install segno
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won\'t be maintained after that date. A future version of pip will drop support for Python 2.7.
You are using pip version 19.0.3, however version 19.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

# アアアッ、すみません

% which pip
/Users/otiai10/.venv/default/bin/pip

# venv使っててこれかよ

% cd ~
% rm -rf .venv
% mkdir ~/.venv
% cd .venv
% virtualenv create default -p /usr/local/bin/python3
% source ./default/bin/activate
% which pip
/Users/otiai10/.venv/default/bin/pip
% python -V
Python 3.6.8

# オッケ

% pip install segno
% segno --help
% segno "https://before11.hatenablog.com/" -o myblog.png
% gat myblog.png

# 小せえ!

% segno "https://before11.hatenablog.com/" -o myblog.png --scale 8
% gat myblog.png

# いい感じ

ついでなので上記のログに出てきた gat を紹介させてください。

github.com

DRYな備忘録として

Node.jsのchild_process間で、標準出力のpipe

これをやりたい↓

cat ./testdata.txt | grep otiai

JavaScriptで、

const cat = spawn('cat', ['./testdata.txt']);
const grep = spawn('grep', ['otiai']);
cat.stdout.pipe(grep.stdin);
grep.stdout.on('data', (chunk) => {
    console.log('[grep]');
    console.log(chunk.toString());
});

f:id:otiai10:20190530121345p:plain

おわりです。うごくやつです。

github.com

DRYな備忘録として

Node.jsデザインパターン 第2版

Node.jsデザインパターン 第2版

Node.js超入門[第2版]

Node.js超入門[第2版]

はじめてUNIXで仕事をする人が読む本

はじめてUNIXで仕事をする人が読む本

ElectronデスクトップアプリによるGoogleのOAuth2クライアント実装

目的

  • 僕が、ElectronデスクトップアプリによるOAuthプロセスを知る

うるせえ動くもん見せろ

はい。

github.com

ゴール

  • 手元のElectronアプリで、ログインユーザ(この場合、僕自身)のAPIトークンで、GoogleのなんらかのAPIが叩ける
    • ← 登録したアプリ下でわざわざ作成したAPIトークンではないのがミソ

本稿でやらないこと

  • OAuthとはなにかのもっとわかりやすいやつ(こんど別エントリで書きたい)
  • 得られたAPI Tokenの永続保存(electron-json-storageでも使う)
  • Expire時のRefreshの実装 (割愛)

参考資料

目次

  1. デスクトップアプリの設計概要
  2. Google Cloud Console での準備
  3. 最低限のElectronアプリと動線を作成
  4. OAuthクライアントの実装

1. デスクトップアプリの設計概要

こういうものをつくっていきます

f:id:otiai10:20190502154537j:plain
アプリのペーパープロトタイプ(検索の索をまちがえている)

  1. メインのウィンドウでトップ画面が開く
  2. Googleでログイン」的なボタンを押すと別ウィンドウが開く
  3. そこでGoogleにログインして、権限も許可する
  4. 成功するとそのウィンドウは消えて、メインのウィンドウで認証情報が得られる
  5. なんらかのAPI(たとえばYouTube検索API)が使える

みたいな感じで。

2. Google Cloud Console での準備

この手順はもちろん「OAuthプロバイダ」によって操作が異なります

が、

OAuthのプロトコルに則る以上、以下のものを得る・設定する手順には変わりありません

  1. クライアントID(ひつようですね)
  2. クライアントシークレット(ひつようですね)
  3. リダイレクトURI(指定可能だが、登録するひつようがある)

今回は、GoogleをOAuthプロバイダとしてこれをやっていきます。

OAuthプロトコルとしては些末なことなのでここに折りたたんでおきます😉

ⅰ) プロジェクトを作成 - Google Cloud Platform ここに行く

f:id:otiai10:20190502160416p:plain
Google Console 上で新しいプロジェクトを作成(名前なんでもいい)
ⅱ) 使う予定のAPIを有効化 f:id:otiai10:20190502160658p:plain:w320 f:id:otiai10:20190502160725p:plain:w320 f:id:otiai10:20190502160935p:plain:w320
ⅲ) (番外)アプリのAPIトークンで最終目的地を確認
これは番外なんですが、とりあえずYouTubeAPIが叩けることを確認するために、アプリのAPIトークンを作成して、APIが叩けているということをクリアにしておきたい。個人的な性癖です。でもだいじ。こういうのだいじ。 f:id:otiai10:20190502161204p:plain:w320 認証情報、から f:id:otiai10:20190502161537p:plain APIキーのほうを作成して、 f:id:otiai10:20190502161723p:plain で、`curl`で試してみる。 - Search: list  |  YouTube Data API  |  Google Developers
f:id:otiai10:20190502162551p:plain
curlで叩けている(jqとかはノリです)
User Token の場合は、`Authorization Header`に入れる必要があるが、とりあえずこのAPIを叩けることは確認できた。
ⅳ) OAuthクライアントを登録
f:id:otiai10:20190502164911p:plain f:id:otiai10:20190502165218p:plain f:id:otiai10:20190502165414p:plain このように得られました。

はい!と、いうことでね!上記3点を得ることができました!

3. 最低限のElectronアプリと動線を作成

ファイルぜんぶここで晒すのめんどくさいので、そのdiffをおいておきます。

Implement minimum transitions btw windows · otiai10/electron-playground@7085aed · GitHub

ポイントとしては、Electronにはmainプロセスとrendererプロセスがあり、rendererプロセスを起因としてGoogleの認証画面を開かせる流れなので、

  • トップ画面のrendererプロセスは「Googleでログイン」ボタンをきっかけとして、mainプロセスへipcRenderer.send('auth-start')などのメッセージを送る
  • mainプロセスは、ipcMain.on('auth-start', () => ...)などでその認証プロセスをスタートさせる
    • mainプロセスは、あたらしい画面(BrowserWindow)をつくり、Googleのページへ誘導する
    • この画面におけるGoogleとのやりとりが成功したトリガーを検知し、成果物を取得し、この画面を閉じる
    • mainプロセスは、この成果物をトップ画面へ win.send('auth-success', tokens)などで通知する
  • トップ画面は ipcRenderer.on('auth-success, tokens => ...)などでこのメッセージを受け取る
    • トップ画面は、トークンが得られたら、「Googleでログイン」ボタンを非表示にし、かわりに主なコンテンツを表示する

4. OAuthクライアントの実装

ファイルぜんぶここで晒すのめんどくさいので、そのdiffをおいておきます。

Implement OAuth handshake inside the app · otiai10/electron-playground@5f9b18e · GitHub

ポイントをいくつか列挙すると

  • これべんり GitHub - googleapis/google-auth-library-nodejs: 🔑 Google Auth Library for Node.js
  • プロバイダ(Google)画面における認証と権限許可が成功したことをデスクトップアプリでどう検知するか
    • ウェブサービスであれば、Callback URLないしRedirect URIなどと呼ばれるURLに、ブラウザがリダイレクトされる
    • ウェブサービスではない。BrowserWindowfile://から始まるURLにリダイレクトさせることを考えるが、これはプロバイダ側から許可されないことが多い
    • 苦肉の策ではあるが、BrowserWindowpage-title-updatedイベントを使い、Google上の認証認可が成功したことを検知する
  • 検知したら、codeを取得し、これをaccess tokenと交換してもらう
    • これはgoogle-auth-libraryがやってくれる。べんり。
  • あとはこれをrendererプロセスへ通知し、rendererプロセスにおいてYouTube Data API / Searchを叩けばよい

完成したものです

雑感

  • page-title-updatedとかダサすぎか
  • <template>タグべんり
  • fetchべんり
  • async/awaitべんり
  • OAuthについて学んだことをまとめようかと思ったけどちょっとだるいなこれは
  • なんか最近「おちついてやる」という能力が板についてきて、よい
  • GW長い
  • 令和!

DRYな備忘録として

Elasticsearch: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

tl;dr

Elasticsearchが動くコンテナの中に以下の環境変数をねじこめばよい。

参考: Running Elasticsearch 5 - Build Environment - CircleCI Discuss

# これ
# transport.host=localhost
#
# と、これ
# bootstrap.system_call_filter=false
# 
# docker run で渡す場合は、

docker run -d \
    -e transport.host=localhost \
    -e bootstrap.system_call_filter=false \
    -p 9200:9200 docker.elastic.co/elasticsearch/elasticsearch:6.6.1

# となる

問題

  • CircleCI上でElasticsearchのDockerイメージがうごいてくれない
ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

原因

vm.max_map_countとは、

1プロセスあたり所有できるメモリマップの数で、/etc/sysctl.confで指定できる。

解決(ホストをいじれる場合)

ので、ホストの/etc/sysctl.confをいじって許容を広げればよい。

sudo sysctl -w vm.max_map_count=262144

しかしながら、CircleCIインスタンス上ではこれはpermission deniedとなる。さもありなん。

解決(Circle CI)

したがって、なんらかの方法でコンテナ側の要求を下げる必要がある。

docker run -d \
    -e transport.host=localhost \
    -e bootstrap.system_call_filter=false \
    -p 9200:9200 docker.elastic.co/elasticsearch/elasticsearch:6.6.1

あるいは、CircleCI v2 であれば、dockerディレクティブの中で

docker:
  - image: docker.elastic.co/elasticsearch/elasticsearch:6.6.1
     environment:
         transport.host: localhost
         bootstrap.system_call_filter: false

などとすればよい。

DRYな備忘録として