明日も楽をするために

めんどくさがりなITエンジニアが書くメモ帳

苦しんで覚えるHtml5Audioの世界

その昔スマホWebブラウザで音声を再生する際に苦労したので、その時のメモを公開します(供養)

  • onmousedown、onmouseup、onclick、ontouchstart、ontouchendが音声再生のトリガーになる(追記 touchstartだとトリガーにならないAndroidOS5~6が出現)
  • iOS6:iPhone4:audio.currentTime = 0;はinvalid state error dom exception 11(音声を読み込まないとアクセスできない?)
Android2.2~2.3 webkit533
Android3.0~4.2 webkit534
iOS6            webkit536
Android4.3      webkit537
  • memo
var audio = new Audio();
audio.play();
audio.load();

どちらか片方だけトリガーイベントで呼び出せばいけるみたい
例えば

window.addEventListener('touchstart', function (e) {
    audioPlayer.load();
});

こんな感じで一度呼び出して先にloadすればplayはトリガーイベント以外でも動作するっぽい
Androidで音声再生時にリファラーが送られない時がある(.htaccessが使えない)6系は大丈夫?
Android4.1.2で音声の先頭または後方が途切れる場合がある

audioとvideo再生時にUAが変わる

audioとvideo取得時にアクセスするUAがwebviewの物とは異なる
Androidの場合(Zenfone5)
stagefright/1.2

iPhoneの場合(iPhone6の9.2)
AppleCoreMedia/1.0.0.13D15 (iPhone; U; CPU OS 9_2_1 like Mac OS X; ja_jp

端末

機種名 version 対応音声 備考
AQUOS PHONE SH-07D 4.0.4 wav 非対応端末
MEDIAS X N-04E 4.1.2 wav ogg 再生確認 快適
ARROWS X F-02E 4.1.2 wav ogg 音が小さすぎて判別不可能 キーボード操作が難しい
AQUOS PHONE EX SH-04E 4.1.2 wav ogg ログを見ると再生できているようだが、音が全く聞こえない
Xperia A SO-04E 4.1.2 wav ogg ログを見ると再生できているようだが、音が全く聞こえない
MEDIAS X N-04E 4.1.2 wav ogg スクロールがしにくい、音は問題ない
AQUOS PHONE ZETA SH-06E 4.2.2 wav ogg ログを見ると再生できているようだが、音が全く聞こえない
ELUGA P P-03E 4.2.2 wav ogg ログを見ると再生できているようだが、音が全く聞こえない
GALAXY J SC-02F 4.4.2 wav ogg mp3 特に問題なし
Xperia Z SO-02E 4.4.2 wav ogg 特に問題なし 快適

Android端末調査

調査項目

  • インスタンスの生成はメソッド内で生成が必要かどうか
  • 基本的には1音再生で、インスタンスは一つが鉄則
  • Android4.1以下は事前ロードが可能
  • EventListenerでendedが発火しない場合に無名関数をやめると動く
  • freac-1.0.26-binでmp3からoggに変換
  • oggファイルをCBR、ABR方式にすることによって音声が途中で切れる現象を防ぐことを確認4.1.2(デフォルトはVBRだと思わる。再現性あり
  • oggファイルのビットレート方式によってendedが発生しないパターンがあるが、原因不明
  • touchstartだとトリガーにならない端末がAndroidである(5~6系)
機種名 version インスタンスの事前生成 戻るボタン操作後の動作 備考
MEDIAS U N-02E 4.0.4 - 事前読み込みができることを確認
Xperia UL SOL22 4.1.2 - 事前読み込みができることを確認、事前読み込みしないと再生できない、oggによって一部途切れる
MEDIAS X N-04E 4.1.2 - 事前読み込みができることを確認、事前読み込みしないと再生できない、oggによって一部途切れる
Galaxy S4 SC-04E 4.2.2 音声が再生される 画面遷移できない(イベントリスナーバグ)、ABRでendedが発火しない?VBRは発火する
ELUGA P P-03E 4.2.2 音声が再生される -
Xperia Z1 SO-01F 4.4.2 画面遷移できない(イベントリスナーバグ)
Asus Zenfone5 5.0 - oggファイルによってendedが発火しない(Adobe MediaEncoderで生成)
AQUOS U SHV37 6.0.1 - -
iPhone6 8.4 音声はタップしても再生しない -

イベントリスナーが発火しないパターン
Xperia Z1 SO-01F

audio.addEventListener('error',this.jump,false); ○
audio.addEventListener('error',jump,false); ×
audio.addEventListener('ended', function foo() { ○
    jump();
}, false);
audio.addEventListener('ended', function() { ×
    jump();
}, false);

■Galaxy S4 SC-04E

audio.addEventListener('error',this.jump,false); ○
audio.addEventListener('error',jump,false); ×
audio.addEventListener('ended', function foo() { ○
    jump();
}, false);
audio.addEventListener('ended', function() { ○
    jump();
}, false);

4.1.2の奇妙なバグ(一度先に何かしら読み込めばあとは問題ない)
Xperia UL SOL22

audio.src = sounds[0]; ○
audio.load();
function onClick() {
    audio.play();
}
 
function onClick() {
    audio.src = sounds[0]; ×
    audio.load();
    audio.play();
}

oggファイルについて

Sampleコード

基本的にaudioをコードを極力かかないことでエラーを防ぐ

var audio = new Audio();
//srcに指定した段階で勝手に読み込みを始めるがスマホはタッチ制約があるため読み込みを基本的に行わない
//Android4.1以下は例外的に読み込みを始める。むしろAndroid4.1以下の時は何かしら読み込みをしないと音声操作が何もできなくなる。
//(たぶん内部的にソースがないのでExceptionを吐いてる可能性・・・)
//初期化時に指定してもいいしタップ時に指定しても動くが出来れば、タップ時にsrcに指定を推奨
//audio.src = "http://foo.com/foo.mp3";
 
function play() {
    //ここでstopを呼び出してもよい

    //canplaythroughは音声のロードが終わった際に発生するevent
    //読み込み終了時のイベントは色々あるが、端末によってイベントの発生タイミングが異なるため(かなり違う
    //単純にどの端末でも音声読み込み後に発生する可能性が高いイベントがcanplaythroughである
    //通常はplayを呼び出すだけで、読み込みと再生を行ってくれるが、読み込み前にplayを実行すると落ちたりエラーになる端末があるため(ARROWSとか)

    //第二引数に指定する関数は無名関数だと呼ばれない端末が存在するので、必ず適当な名前をつける。
    //他に記述した関数を呼び出す際は必ずthisをつける(例:this.stop)
    audio.addEventListener("canplaythrough", function local() {
        //一応消しておく本来は何回addしても問題ないはずだが一応・・・
        audio.removeEventListener("canplaythrough", local, false);
        audio.play();
    }, false);
    audio.src = "http://foo.com/foo.mp3";
    audio.load();
}
 
function stop() {
    audio.pause();
    //↓音声を読み込む前に時間を移動させるとExceptionを吐く端末がある(新しい端末は吐かない)
    try {audio.currentTime = 0;} catch(e) {}//iOS6、Android4.2対応
}

音声が再生できない際のチェック項目

  • 音声再生に対応しているか?(基本的には対応していると思うが。。。。WebAudioAPIだとインスタンスが生成できても音が一切ならない端末もあるらしい
  • 音声ファイルは対応しているか?(iPhoneは基本はmp3、Androidoggが基本だが稀にmp3が再生できる端末もある)
  • トリガーをちゃんと使用しているか?(onClick,touchend)
  • urlをaudioに渡すタイミングは適切か?(srcに渡した段階でloadが始まるのがデフォルトだが(スマホはトリガー次第でロードしない)、それで予期しない動きをすることがある
  • audio再生時にちゃんと音声が読み込まれているのを確認した上でplayを呼び出しているか?(loadが全部終わらない前にplayするとクラッシュする端末あり)
  • 指定のeventリスナーは発火しているか?(endedなど音声ファイルによって発火しない場合あり)
  • その音声ファイルは本当に問題ないか?(oggビットレートの変換タイプ(VBR,ABR,CBR)によって動作が変わる場合がある

S3で一定時間オブジェクトにアクセス可能なURLの生成

プライベート制限しているバケットのオブジェクトをいつもはGetObjectでデータを取得してbase64エンコードに加工して~
みたいな事をやっていたのですが、どうやら一定時間だけアクセスを許可した署名付きURLが発行できる仕組みがあったので共有します

<?php
//Sample Code
$s3 = S3Client::factory([
    'credentials' => [
        'key' => $accessKey,
        'secret' => $secretKey,
    ],
    'version' => 'latest',
    'region'  => 'ap-northeast-1',
]);

$cmd = $s3->getCommand('GetObject', [
    'Bucket' => 'my-bucket',
    'Key' => 'testKey'
]);

$request = $s3->createPresignedRequest($cmd, '+20 minutes');
echo $presignedUrl = (string)$request->getUri();

AWS SDK for PHP バージョン 3 での Amazon S3 の署名付き URL - AWS SDK for PHP

aws-sdk-phpでIDCFとMinioのオブジェクトストレージに投稿する設定

AWS

aws-sdk-phpの3.x系で動作確認済み

<?php
$s3 = Aws\S3\S3Client::factory(array(
    'credentials' => [
        'key'    => '',
        'secret' => '',
    ],
    'region' => 'ap-northeast-1',
    'version' => '2006-03-01', //latestでもいい
));
$result = $s3->putObject(array(
    'Bucket' => 'bucket-test',
    'Key'    => 'key',
    'Body'   => 'body',
    'ACL'    => 'public-read',
    //'ContentType' => 'image/jpeg',
));
echo $result['ObjectURL'];

Minio

aws-sdk-phpの3.x系で動作確認済み

<?php
$s3 = Aws\S3\S3Client::factory(array(
    'credentials' => [
        'key'    => '',
        'secret' => '',
    ],
    'region' => '', //値はなくても構わないがkeyは必要
    'version' => '2006-03-01', //latestでもいい
    'endpoint' => 'http://127.0.0.1:9000', //minioのipでhttpから記述する必要がある
    'use_path_style_endpoint' => true,
));
$result = $s3->putObject(array(
    'Bucket' => 'bucket-test',
    'Key'    => 'key',
    'Body'   => 'body',
    'ACL'    => 'public-read',
    //'ContentType' => 'image/jpeg',
));
echo $result['ObjectURL'];

IDCF

aws-sdk-phpの2.x系で動作確認済み
※3.x系はsignatureの関係なのか動かない・・・

<?php
$s3 = Aws\S3\S3Client::factory(array(
    'credentials' => [
        'key'    => '',
        'secret' => '',
    ],
    'endpoint' => 'http://ds.jp-east.idcfcloud.com',
    'signature' => 's3', //IDCFはsignatureV4に対応していないのでsignatureV2にする必要がありs3でsignatureV2の指定になる
));
$result = $s3->putObject(array(
    'Bucket' => 'bucket-test',
    'Key'    => 'key',
    'Body'   => 'body',
    'ACL'    => 'public-read',
    //'ContentType' => 'image/jpeg',
));
echo $result['ObjectURL'];

s3はバケットURLが複数ある
例:bucket-testをバケット名とした場合
https://s3-ap-northeast-1.amazonaws.com/bucket-test/
https://bucket-test.s3-ap-northeast-1.amazonaws.com/

aws-sdk-php3.x系だとバケットサブドメインでリクエストが投げられるのでMinioなどipで指定したい場合は'use_path_style_endpoint' => true,を指定してバケット名パスを有効にする必要がある
IDCFもAWSと同じ両方のURLパターンに対応しているがsignatureはまだ完全に対応しきれていないっぽい?

ReactNativeでiOS実機デバッグを行う

通常デバッグを行う際にreact-native run-iosだとシミュレータが立ち上がりますが、iOSの実機でデバッグをしたくなることがあります。
例えばプッシュ通知のトークン取得や、決済処理など実機でしかできないことがあります。

github.com

npm install -g ios-deploy
react-native run-ios --device "デバイス名"

※有効な開発証明書がMacにインストールされている必要があります

最近のフロントエンド技術はAD広告を採用しているサイトと相性がよくない

1.レスポンシブデザイン
スマホとPC用にレスポンシブデザインを採用している場合に例えば動画広告を張っているとスマホでは表示されてないPCの動画が裏で再生されていることがありユーザの帯域を無駄に使用する。よく分かってない人がスマホの場合はPCの動作を表示しないようにすればいいのでは?というが、それだったら最初からレスポンシブデザインを採用しなければよかったのでは?ちなみに該当の動画広告は非同期に非対応で全て同期で再生しないといけないのでサイトが糞重くなる(動画広告って基本的に同期なの?)

2.SPA(vue.js、react.js)
まず画面遷移が減るのでPV数を元に広告費をもらっている場合は売り上げがさがる。ajaxで外部からdomを取得してきた場合にADが再生されない物も多数ありADを提供している会社の選定も必要になってくるが、システムチームに裁量権がない場合に外部からの鶴の一声で別の広告システムを使うことになったりして詰む。

プログラムで書くAutoLayout

tableViewに対してAutoLayoutを設定する際のサンプルコード

//AutoLayoutを有効化する
self.tableView.translatesAutoresizingMaskIntoConstraints = false

//上下左右に指定
self.tableView.topAnchor.constraint(equalToConstant: self.topAnchor).isActive = true
self.tableView.bottomAnchor.constraint(equalToConstant: self.bottomAnchor).isActive = true
self.tableView.leadingAnchor.constraint(equalToConstant: self.leadingAnchor).isActive = true
self.tableView.trailingAnchor.constraint(equalToConstant: self.trailingAnchor).isActive = true

//横と高さをを指定
self.tableView.heightAnchor.constraint(equalToConstant: self.heightAnchor).isActive = true
self.tableView.widthAnchor.constraint(equalToConstant: self.widthAnchor).isActive = true

//X軸とY軸を指定
self.tableView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.tableView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true

//上部からマージン100を指定
self.tableView.constraint(equalTo: self.topAnchor, constant: 100).isActive = true

iOS9以上で使用できます

3Dプリンタで印刷してもらった(DMM.make編

3Dプリンタには興味があったが買うとなると3Dデータを作ったことがない私がいきなり買うのはハードルが高かったが、どうやら世の中には3Dデータを送れば印刷してくれるサービスがあるしい。

高価格な3Dプリンタを使って印刷してくれるのは中々できない事なので非常にありがたい。今回はDMM.makeさんで印刷してもらうことにした。
make.dmm.com


まず用意する3Dデータだがstl形式のデータが必要だ。今回は3DCADのFusion360を使うことにした。
www.autodesk.co.jp

注意点としては作る際はmm単位で作ることと、z軸を高さとして調節することだった。最初cm単位で作ってしまっていて印刷が出来ないと連絡がきた・・・。
試行を重ねてようやく下記の状態まで作成できた。

f:id:makoto1212:20170713234437p:plain

DMM.makeで使える素材は色々あるのだが今回は一番安いナイロンを選択した。オプションで色の選択や磨きなどがある。
選べる素材一覧 - DMM.make 3Dプリント


f:id:makoto1212:20170713234826j:plain
約一週間ほど待って実際に届いた現物がこちらで想像よりかなり綺麗。このサイズで642円だがオプションに色と磨きをつけると1600円ほどになる。思ったよりも楽しかったので3Dプリンタを今度は買おうと思っている。