フリーランス 技術調査ブログ

フリーランス/エンジニア Ruby Python Nodejs Vuejs React Dockerなどの調査技術調査の備忘録

AWS Elastic BeanstalkでLaravelを動作させる

はじめに

  • EBでLaravelを動作させる機会があったので、下記にまとめました。

ElasticBeanstalk アプリケーション作成

  • 「Create Application」ボタンをクリックする

  • 「アプリケーション名」を設定する

  • PHP、ファイルアップロードを選択する

  • アプリケーションの作成ボタンをクリックする

  • アプリケーションの作成ボタンをクリックすると数分かかるので、しばらく待つ

  • ドキュメントのルートに「/public」を指定する

アップロード用のファイルを作成する

  • アプリケーションフォルダのルート直下で下記のコマンドを実行する
zip ../laravel-default.zip -r * .[^.]* -x "vendor/*" "docker/*"

デプロイ時にマイグレーションを走らせたい場合

  • .platform/hooks/postdeploy/migrate.shファイルを作成し下記の内容を記述する。
sudo chmod -R 777 storage/
sudo chmod -R 777 bootstrap/cache/
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate

Nginxの設定を変更したい場合

  • .platform/nginx/conf.d/elasticbeanstalk/proxy.confファイルを作成し下記の記述を追加する。
client_max_body_size 5000M;

PHP.iniの設定を変更したい場合

  • .ebextensions/change_upload_size.configファイルを作成し下記の記述を追加する
files:
    "/etc/php.d/99uploadsize.ini":
        mode: "000644"
        owner: root
        group: root
        content: |
            upload_max_filesize = 1024M
            post_max_size = 1024M
commands:
    remove_old_ini:
        command: "rm -f /etc/php.d/99uploadsize.ini.bak"

S3に独自ドメインを設定する

はじめに

  • S3にSSL通信でアクセスできるための設定を下記のまとめてみました。

AWS Certificate Managerの設定

  • SSL証明書の定義

  • 証明書をリクエスト「パブリック証明書をリクエスト」を選択し、「次へ」ボタンをクリックする

  • ドメイン名にSSLを設定したいドメイン名を指定する。検証方法の選択は、デフォルトの「DNS検証」を選択したまま、「リクエスト」ボタンをクリックする

  • 証明書の一覧画面に遷移しリクエストした証明書が登録されていることを確認する。ステータスは「保留中の検証」の状態であることを確認する

CloudFrontの設定

  • ディストリビューションを作成」ボタンをクリックする

  • オリジンドメインに対象となる「S3」のバケットを選択する。S3のバケットアクセスは「Origin access control settings (recommended)」を選択する

  • デフォルトのキャッシュビヘイビアはデフォルトの状態にする

  • ACLの指定があるが値は指定しなくてもよいがIAMのポリシーに「WAF2」の権限の指定が必要。代替ドメイン名とカスタムSSL証明書を設定する

Route53の設定

AWS Elemental MediaConvertの設定

はじめに

AWS Elemental MediaConvertを利用する機械があったので、設定する手順を下記に記載しました。

ジョブテンプレートの作成

- 一般設定を下記のように任意の値を入力する - 入力は何も設定せず、デフォルトのまま

  • 出力の追加ボタンをクリックし「Apple HLS」を選択する

  • 変換後のファイルをアップロードするS3を指定する

  • 「名前修飾子」に「_hls」を追加する

  • 画面右上の赤枠のメニューをクリックし設定画面に遷移し、ビデオコーデックなどの設定を行い、作成ボタンをクリックする
ビデオコーデック:MPEG-4 AVC (H.264)
解像度:1280 x 720
フレームレート:30fps(1秒間に30回更新されるという意味です。)
ビットレート:5Mbps(1秒間の転送データ量です。)
ピクセルアスペクト比:16:9

ロール作成

  • ロール作成ボタンをクリックし「他の AWS のサービスのユースケース」に「MediaConvert」を選択し「次へ」ボタンをクリックする

- 何も変更せずに「次へ」ボタンをクリックする

  • 任意のロール名を指定して「ロール作成」ボタンをクリックする

S3のオブジェクト所有者の設定

プログラムで利用するJob作成用Jsonを出力する

  • 出力されたJSONファイルで下記の部分だけ抽出する
    "OutputGroups": [
      {
        "Name": "Apple HLS",
        "Outputs": [
          {
            "ContainerSettings": {
              "Container": "M3U8",
              "M3u8Settings": {}
            },
            "VideoDescription": {
              "Width": 1280,
              "Height": 720,
              "CodecSettings": {
                "Codec": "H_264",
                "H264Settings": {
                  "ParNumerator": 16,
                  "FramerateDenominator": 1,
                  "MaxBitrate": 50000,
                  "ParDenominator": 9,
                  "FramerateControl": "SPECIFIED",
                  "RateControlMode": "QVBR",
                  "FramerateNumerator": 30,
                  "SceneChangeDetect": "TRANSITION_DETECTION"
                }
              }
            },
            "AudioDescriptions": [
              {
                "AudioSourceName": "Audio Selector 1",
                "CodecSettings": {
                  "Codec": "AAC",
                  "AacSettings": {
                    "Bitrate": 96000,
                    "CodingMode": "CODING_MODE_2_0",
                    "SampleRate": 48000
                  }
                }
              }
            ],
            "OutputSettings": {
              "HlsSettings": {}
            },
            "NameModifier": "_hls"
          }
        ],
        "OutputGroupSettings": {
          "Type": "HLS_GROUP_SETTINGS",
          "HlsGroupSettings": {
            "SegmentLength": 10,
            "Destination": "s3://<your bucket>/",
            "MinSegmentLength": 0
          }
        }
      }
    ],

次回

  • 上記で設定が完了したので、次はプログラムからAWS Elemental MediaConvertをプログラムから読んでみたいと思います。

Reactで動画再生する

はじめに

  • お仕事でReactで動画再生する方法を検討する
  • 調査したいこと、動画開始、終了イベント及び再生時間の取得ができるか

該当プラグイン

調べたところ下記の2つのプラグインがあることを知りstar数の数の多いreact-playerを検証してみる。

  • video-react  startの数:2.3k

https://github.com/video-react/video-react

  • react-player startの数:6.7k

https://github.com/cookpete/react-player https://github.com/cookpete/react-player/issues/1474

サンプルコード

import React, { useState, useEffect, useRef } from 'react'
//import ReactPlayer from 'react-player/lazy'
import dynamic from "next/dynamic";
const ReactPlayer = dynamic(() => import("react-player/lazy"), { ssr: false });
// https://video-ac.com/video/4648
// https://www.pexels.com/ja-jp/search/videos/%E9%A2%A8%E6%99%AF/?size=small&orientation=landscape
/*
https://stackoverflow.com/questions/68374975/get-total-played-time-with-react-player
*/
const Movie = () => {
  const [hasWindow, setHasWindow] = useState(true);
  const [started, setStarted] = useState<number>(0);
  const [ended, setEnded] = useState(0);
  const [isDisplay, setIsDisplay] = useState(false);
  useEffect(() => {
    if (typeof window !== "undefined") {
      setHasWindow(true);
    }
  }, []);

  const start = () => {
    const start = new Date()
    setStarted(start.getTime());
  };
  const end = () => {
    const end = new Date()
    setEnded(end.getTime());
    setIsDisplay(true);
  };    
  return (
    <>
    <div className="static">
      <div className="flex justify-center mx-auto bg-white shadow-lg shadow-none rounded m-6 p-6">
          {hasWindow && 
          <ReactPlayer 
          url={'<動画のURLがはいります>'} 
          controls={true}
          muted={true}
          onStart={start}
          onEnded={end}
          volume={1}
        />}
      </div>
      <div className="flex justify-center mx-auto bg-white shadow-lg shadow-none rounded m-6 p-6">
          {(ended - started > 0) ? `${(ended - started) / 1000}秒` : started === 0 ?  '再生前' : `再生中` }
      </div>
      <div className="absolute top-2 left-96">
        <div style={{display: isDisplay ? "block": "none"}} id="defaultModal" tabIndex={-1} className="overflow-y-auto overflow-x-hidden inset-0 z-50 top-10 flex justify-center items-center" aria-modal="true" role="dialog">
          <div className="relative p-4 w-full max-w-2xl h-full md:h-auto">
              <div className="relative bg-white rounded-lg shadow dark:bg-gray-700">
                  <div className="flex justify-between items-start p-4 rounded-t border-b dark:border-gray-600">
                      <h3 className="text-xl font-semibold text-gray-900 dark:text-white">
                          視聴完了
                      </h3>
                      <button type="button" className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-toggle="defaultModal">
                          <svg aria-hidden="true" className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd"></path></svg>
                          <span className="sr-only">Close modal</span>
                      </button>
                  </div>
                  <div className="p-6 space-y-6">
                    <div className="flex items-center pl-4 rounded border border-gray-200 dark:border-gray-700">
                        <input id="bordered-radio-1" type="radio" value="" name="bordered-radio" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                        <label htmlFor="bordered-radio-1" className="py-4 ml-2 w-full text-sm font-medium text-gray-900 dark:text-gray-300">良かった</label>
                    </div>
                    <div className="flex items-center pl-4 rounded border border-gray-200 dark:border-gray-700">
                        <input checked id="bordered-radio-2" type="radio" value="" name="bordered-radio" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                        <label htmlFor="bordered-radio-2" className="py-4 ml-2 w-full text-sm font-medium text-gray-900 dark:text-gray-300">普通</label>
                    </div>
                    <div className="flex items-center pl-4 rounded border border-gray-200 dark:border-gray-700">
                        <input checked id="bordered-radio-2" type="radio" value="" name="bordered-radio" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                        <label htmlFor="bordered-radio-2" className="py-4 ml-2 w-full text-sm font-medium text-gray-900 dark:text-gray-300">悪かった</label>
                    </div>
                  </div>
                  <div className="flex items-center p-6 space-x-2 rounded-b border-t border-gray-200 dark:border-gray-600">
                      <button data-modal-toggle="defaultModal" type="button" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" onClick={() => {setIsDisplay(false)}}>設定</button>
                  </div>
              </div>
          </div>
        </div>
      </div>
      </div>      
    </>
  )  
}

export default Movie

画面

  • 再生開始、終了イベントの取得はできたが、再生時間の取得がうまくできなかった。

結論

  • videojsだと再生開始・終了イベントを取得でき、且つ再生している時間を取得できるので、videojsがよさそうという結論になった
https://codesandbox.io/s/react-videojs-currenttime-zvlbn?file=/src/VideoPlayer.js:1070-1081
https://videojs.com/guides/react/
https://videojs.com/advanced?video=disneys-oceans

Flutterでタイマーアプリをつくる

はじめに

  • 下記の動画を参考に自分のアレンジを入れてタイマーを作成してみました。 www.youtube.com

サンプルコード

import 'dart:ui';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:iterative_memory/widget/button_widget.dart';

class TimerPage extends StatefulWidget {
  @override
  _TimerPageState createState() => _TimerPageState();

}

class _TimerPageState extends State<TimerPage> {
  final player = AudioPlayer();

  //static const defaultSeconds = 10;
  int maxSeconds = 10;
  int seconds = 10;
  Timer? timer;

  void startTimer({bool reset = true}) {
    if (reset) {
      resetTimer();
    }
    // seconds: 1
    timer = Timer.periodic(Duration(seconds: 1), (_){
      if (seconds > 0) {
        setState(() => seconds--);
      } else {
        stopTimer(reset: false);
        seconds = maxSeconds;
      }
    });
  }

  void resetTimer() => setState(() => seconds = maxSeconds);

  void stopTimer({bool reset = true}) {
    if (reset) {
      resetTimer();
    }
    setState(() => timer?.cancel());
  }

  @override
  Widget build(BuildContext context) => Scaffold(
      body: Container(
          width: double.infinity,
          color: Colors.deepPurpleAccent,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              buildTimer(),
              const SizedBox(height: 80),
              buildButtons()
            ],
          )
      ));

  Widget buildButtons() {
    final isRunning = timer == null ? false : timer!.isActive;
    final isCompleted = seconds == maxSeconds || seconds == 0;
    final timeController = TextEditingController();

    return isRunning || !isCompleted ? Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ButtonWidget(
              text: isRunning ? '停止' : '再開',
              onClicked: () {
                if (isRunning) {
                  stopTimer(reset: false);
                } else {
                  startTimer(reset: false);
                }
              }),
          const SizedBox(width: 12),
          ButtonWidget(text: '初めから', onClicked: () {
            resetTimer();
          })
        ]
    ) : Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          decoration: InputDecoration(
              fillColor: Colors.white,
              filled: true,
              border: OutlineInputBorder(),
              hintText: '秒数を入力してください'
          ),
          keyboardType: TextInputType.number,
          autofocus: true,
          controller: timeController,
        ),
        ButtonWidget(
          text: 'スタート',
          color: Colors.black,
          backgroundColor: Colors.white,
          onClicked: () {
            maxSeconds =  int.parse(timeController.text);
            startTimer();
          },
        ),
      ]
    );
  }

  Widget buildTimer() => SizedBox(
      width: 200,
      height: 200,
      child: Stack(
        fit: StackFit.expand,
        children: [
          CircularProgressIndicator(
            value: 1 - seconds / maxSeconds,
            valueColor: AlwaysStoppedAnimation(Colors.white),
            strokeWidth: 12,
            backgroundColor: Colors.greenAccent,
          ),
          Center(child: buildTime(),)
        ],
      )
  );

  Widget buildTime() {
    return Text(
        '$seconds',
        style: TextStyle(
            fontWeight: FontWeight.bold,
            color: Colors.white,
            fontSize: 80
        )
    );
  }
}
  • Buttonを共通化するためにcomponent化
import 'package:flutter/material.dart';

class ButtonWidget extends StatelessWidget {
  final String text;
  final VoidCallback onClicked;

  var backgroundColor;
  var color;

  ButtonWidget({
    Key? key,
    required this.text,
    this.color = Colors.white,
    required this.onClicked,
    this.backgroundColor = Colors.black,
  }) : super(key: key) {

  }

  @override
  Widget build(BuildContext context) => ElevatedButton(
    style: ElevatedButton.styleFrom(
      primary: backgroundColor,
      padding: EdgeInsets.symmetric(horizontal: 32,vertical: 16)
    ),
    onPressed: onClicked,
    child: Text(
      text,
      style: TextStyle(fontSize: 20, color: color)
    ),
  );

}

画面イメージ

  • 秒数を入れてスタートボタンをクリックするとタイマーが開始します。

  • タイマーが始まり終わると、入力画面に戻るようになっております。

Google reCAPTCHAの設定/ amplifyのauthの設定 

Google reCAPTCHA

reCAPTCHA設定

https://www.google.com/recaptcha/intro/v3.html

画面上部の中央にある「Admin console」をクリックします。

amplifyに認証設定する

amplify add authのコマンドを実行し下記の選択を行う

$ amplify add auth
Using service: Cognito, provided by: awscloudformation
 Do you want to use the default authentication and security configuration? [Default configuration]

 How do you want users to be able to sign in? [Email]

 Do you want to configure advanced settings? [Yes, I want to make some additional changes.]

※default以外に登録に必要な属性は何ですか?→誕生日、性別などを追加指定しました。
 What attributes are required for signing up? [Birthdate (This attribute is not supported by Login With Amazon, Signinwithapple.), Email, Gender (This attribute is not s
upported by Login With Amazon, Signinwithapple.), Nickname (This attribute is not supported by Facebook, Google, Login With Amazon, Signinwithapple.), Updated At (This
attribute is not supported by Google, Login With Amazon, Signinwithapple.)]

※ 以下の機能のいずれかを有効にしますか? →「Google reCaptcha」を追加する
 Do you want to enable any of the following capabilities? [Add Google reCaptcha Challenge]

Do you want to edit your captcha-define-challenge function now? [Yes]
/<app_root>/amplify/backend/function/winlogic1d56dd94DefineAuthChallenge/src/captcha-define-challenge.js

? Do you want to edit your captcha-create-challenge function now? No
✔ Enter the Google reCaptcha secret key: ·[google reCAPTCHAで取得したシークレットキーを指定する]

? Do you want to edit your captcha-verify function now? [Yes]
/<app_root>/amplify/backend/function/winlogic1d56dd94VerifyAuthChallengeResponse/src/captcha-verify.js

amplify pushする

  • ローカルバックエンドリソースを構築し、クラウドでプロビジョニングします

amplify サインアップサンプル

  • ボタンを押すと、固定のユーザーとなりますが、amplifyのsignUpメソッドを実行するサンプルとなります。
import { Amplify, Auth } from 'aws-amplify';
import awsExports from '../src/aws-exports';
Amplify.configure(awsExports);

import styles from '../styles/Home.module.css';

async function signUp() {
  try {
   // 更新日付を取得
    const date = new Date();
    const unixtimeUpdatedAt = date.getTime() ;

    const { user } = await Auth.signUp({
            username: "<your email>",
            password: '<your password>',
            attributes: {
              email: "<your email>",
              gender: 'm',
              nickname: 'hogehoge',
              birthdate: '<your birthdate>',
              updated_at: unixtimeUpdatedAt.toString()
            }
        });
        console.log(user);
    } catch (error) {
        console.log('error signing up:', error);
    }
}

export default function signingUp() {
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <button onClick={signUp} className="shadow bg-teal-400 hover:bg-teal-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded" type="button">
          Signing Up!!
        </button>
      </main>
    </div>
  );
}

次回

  • フォームからユーザーを登録できるようにする。またGoogle reCAPTCHAを導入にチャレンジジョイしてみます。

amplifyの環境でエラーが発生したので再度新しくサイトを構築

はじめに

  • amplifyの開発を進めていたが、デプロイ時のエラーをどうしても解決できず、1度環境を壊して再構築した。

windowsのwsl環境で開発しております。

ローカルのnodejsを削除とインストール

  • 環境を最新にする
sudo apt-get update
  • nodejsアンインストール
npm uninstall -g npm
sudo apt-get remove nodejs
  • nvm削除
rm -rf $NVM_DIR

-nvm install

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
※v.039.1の箇所は最新バージョンを指定するようにする

nvm install node

Amplify Setup Instructions

STEP1 amplify configure

$  amplify configure
? region:  ap-northeast-1
? user name:  (amplify-vINdB) <specify any user name>
※Here open browser.AWS Console setting completely.Complete the configuration in the AWS Console.
? accessKeyId:  ********************
? secretAccessKey:  ****************************************
? Profile Name:  <specify any user name>

STEP2 amplify init

$ amplify init
? Initialize the project with the above configuration? [No]
? Enter a name for the environment [prod]
? Choose your default editor: [Visual Studio Code]
? Choose the type of app that you're building [javascript]
Please tell us about your project
? What javascript framework are you using [react]
? Source Directory Path:  [src]
? Distribution Directory Path: [.next]
? Build Command: [npm run-script build]
? Start Command: [npm run-script start]
Using default provider  awscloudformation
? Select the authentication method you want to use: [AWS profile]

STEP3 amplify add api

$ amplify add api
? Select from one of the below mentioned services: [GraphQL]
? Here is the GraphQL API that we will create. Select a setting to edit or continue [Continue]
? Choose a schema template: [One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)]
✔ Do you want to edit the schema now? (Y/n) · [yes]

下記のように既存のschemaを変更してみた。

input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @hasMany
  categories: [Category] @hasMany
}

type Category @model {
  id: ID!
  name: String!
  isPublished: Boolean!
  blog: Blog @belongsTo
  post: Post @belongsTo
}

type Post @model {
  id: ID!
  title: String!
  body: String!
  created_at: AWSDateTime!
  updated_at: AWSDateTime!
  isPublished: Boolean!
  blog: Blog @belongsTo
  categories: [Category] @hasMany
  comments: [Comment] @hasMany
}

type Comment @model {
  id: ID!
  post: Post @belongsTo
  content: String!
}

STEP4 amplify push

$ amplify push
? Are you sure you want to continue? [Yes]
? Do you want to generate code for your newly created GraphQL API [Yes]
? Choose the code generation language target [typescript]
? Enter the file name pattern of graphql queries, mutations and subscriptions [src/graphql/**/*.ts]
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions [Yes]
? Enter maximum statement depth [increase from default if your schema is deeply nested] [5] ※デフォルトは2だが、念のため5階層に
? Enter the file name for the generated code [src/API.ts]

# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

STEP5 amplify hosting add

✔ Select the plugin module to execute · [Hosting with Amplify Console (Managed hosting with custom domains, Continuous deployment)]
? Choose a type [ Continuous deployment (Git-based deployments)]
※The browser starts up here.

STEP6 amplify publish

? Are you sure you want to continue? Yes

参考文献

docs.aws.amazon.com

https://docs.amplify.aws/cli/teams/overview/

blog.serverworks.co.jp

www.ragate.co.jp

blog.serverworks.co.jp

serverless.co.jp

docs.aws.amazon.com

qiita.com