Amazon EBS ボリュームの拡張が本当にダウンタイムなしでできた

ボリュームサイズ変更後の Linux ファイルシステムの拡張 - Amazon Elastic Compute Cloud

↑のドキュメント見ながらやるだけです。

ファイルシステムがXFSなのかExt4なのかで、一部コマンドが違うので注意が必要ですが、特に難しいことはありませんでした。

最近ECSしか使ってないので、急に古いシステムのメンテナンスがあるとドキッとします。

ExoPlayerでhlsを流してみる

まずは、codelabsからコードをダウンロードしてきます。

https://developer.android.com/codelabs/exoplayer-intro?hl=ja#0

このコードラボのサンプルだとmp4とDASHの形式しか扱わないので、コードの修正が必要になります。

まずは、exoplayer-codelab-04を有効化(インポート)するのがいいと思います。

app/build.gradleを下記のように変更します。

// ここだけ抜粋
dependencies {
    // Switch out the code lab step to jump to different points in the code lab
    implementation project(':exoplayer-codelab-04') // ←だけ変更
    implementation 'androidx.multidex:multidex:2.0.1'
}

また、hls用のライブラリを読み込まないといけないので、 exoplayer-codelab-04/build.gradle に下記のように追加します。

そしてこの辺りを

    private fun initializePlayer() {
        // ExoPlayer implements the Player interface
        player = ExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
                // Update the track selection parameters to only pick standard definition tracks
                exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
                        .buildUpon()
                        .setMaxVideoSizeSd()
                        .build()

                val mediaItem = MediaItem.Builder()
                    .setUri(getString(R.string.media_url_dash))
                    .setMimeType(MimeTypes.APPLICATION_MPD)
                    .build()
                exoPlayer.setMediaItems(listOf(mediaItem), mediaItemIndex, playbackPosition)
                exoPlayer.playWhenReady = playWhenReady
                exoPlayer.addListener(playbackStateListener)
                exoPlayer.prepare()
            }
    }

このように修正します

    private fun initializePlayer() {
        // ExoPlayer implements the Player interface
        player = ExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
                // Update the track selection parameters to only pick standard definition tracks
                exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
                        .buildUpon()
                        .setMaxVideoSizeSd()
                        .build()

                val mediaItem = MediaItem.Builder()
                    .setUri(getString(R.string.media_url_hls)) // 適当なhlsのurlを入手してください
                    .setMimeType(MimeTypes.APPLICATION_M3U8)
                    .build()
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.playWhenReady = playWhenReady
                exoPlayer.addListener(playbackStateListener)
                exoPlayer.prepare()
            }
    }

そして、playbackStateListenerの中身は、同じActivity内にこんなふうに定義しています。

private fun playbackStateListener() = object : Player.Listener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        val stateString: String = when (playbackState) {
            ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
            ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
            ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY     -"
            ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -"
            else -> "UNKNOWN_STATE             -"
        }
        Log.d(TAG, "changed state to $stateString")
    }

    // こっちだとhls(多分tsファイル)からメタデータが取れない
    override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
        mediaMetadata.description?.let {
            Log.d(TAG, "cmId: $it")
        }

        super.onMediaMetadataChanged(mediaMetadata)
    }

    // こっちだとhlsからメタデータが取得できる
    @androidx.media3.common.util.UnstableApi
    override fun onMetadata(metadata: Metadata) {
        Log.d(TAG, "metadata: $metadata")
        super.onMetadata(metadata)
    }
}

これは、hls(多分tsファイル)からメタデータを取得するのを今のプロジェクトで行なっていたので、 自分でも行なってみたかったためです。

メタデータはmp4などだと、曲のタイトルや、アーティスト情報にあたるものです。

CloudFront Functionsでリダイレクト

CloudFront + S3の構成のwebサイトでリダイレクト処理をすることになりました。

S3にもリダイレクトの設定ができるようですが、今回はCloudFront Functionsを使いました。

CloudFrontにはWAFの設定などもあるので、CloudFront Functionsを使うことにより設定をより前の方に集めたかったのが、理由です。

実際に作成した関数は下記を参考にして作っています。

チュートリアル: CloudFront Functions を使用した単純な関数の作成 - Amazon CloudFront

今回は、301でのリダイレクトをしたいと思っているので、statusCode, statusDescriptionを変更し、さらにlocationを目的の場所に設定し直しました。

一見statusDescriptionは不要な気がしますが、実際には省略して、テストすると下記のエラーメッセージが表示されます。

The CloudFront function associated with the CloudFront distribution is invalid or could not run. Invalid status code desc, 

なので、301であれば、statusDescriptionに'Moved Permanently'などを設定すれば良いと思います。

コードはこんな感じです。

function handler(event) {
    var response = {
        statusCode: 301,
        statusDescription: 'Moved Permanently',
        headers: {
            'cloudfront-functions': { value: 'generated-by-CloudFront-Functions' },
            'location': { value: 'https://aws.amazon.com/cloudfront/' }
        }
    };
    return response;
}

最近rails書いてて気づいたこと

railsのEnumerableの拡張が便利

https://guides.rubyonrails.org/active_support_core_extensions.html#extensions-to-enumerable

ActiveRecordのpluckみたいな処理を、hashが要素になっている配列に対して行おうとした時に見つけました。

こういうのは、普通mapでやると思いますが、pluckで書くと若干シンプルになります。

require 'active_support/all'

sample = [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }]
mapped = sample.map { |e| e[:name] }
plucked = sample.pluck(:name) # => ["David", "Rafael", "Aaron"]
p mapped == plucked # true
# 上記2つは同じ結果

sample2 = [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }]
p sample2.map { |e| [e[:id], e[:name]] }
p sample2.pluck(:id, :name) # => [[1, "David"], [2, "Rafael"]]
# 上記2つも同じ結果

vscodeのrubocopのプラグインが、GitHub Copilot並みに便利

というか、rubocopが便利というか、Copilotが何と言おうが、プロジェクトの決まり上、rubocopが正ということになったりもします。

最近の自分のプロジェクトでは、rubocop-railsまで入れてますが、

https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsuniquevalidationwithoutindex

は、db/schema.rb の中身までチェックして、指摘してくれてるらしいです。

それと下記の2つは、こんなメソッド知らなかったみたいなものも教えてくれます

https://docs.rubocop.org/rubocop/cops_style.html#styleeachwithobject

https://docs.rubocop.org/rubocop/cops_style.html#stylefileread

ということで最近はrails書くときは、Copilotは無効化してます。

Android Studio Flamingoにアップグレードした時に古めのプロジェクトで起きたこと

環境

M1 macbook pro

起きたこと1

DSL element 'dexOptions' is obsolete and should be removed.
It will be removed in version 8.0 of the Android Gradle plugin.
Using it has no effect, and the AndroidGradle plugin optimizes dexing automatically.

これはFlamingoにした時にAGPを7.3.1にアップグレードした影響だと思います。これはapp/build.gradleからdexOptionsのブロックを丸々消せるようになりますが、こういうのは地味に嬉しいです。

起きたこと2

How can I fix this error with ButterKnife in Android Studio? - Stack Overflow

↑で起きたことと全く同じ現象です。

いずれは、Butterknifeは使えなくなるんでしょうが、影響範囲が大きすぎておいそれと書き換えたりできないので、悩ましいです。

localstackのバージョンを上げたらSQS周りでハマった

bug: SQS API is not respecting the region defined in request headers · Issue #6102 · localstack/localstack · GitHub

上のissueに関係するものでした。タイトルにbugって書いてあるが、bugじゃなく仕様のようです。

バージョンアップ前は

# 環境変数
SQS_URL=http://localstack:4566/000000000000/app_name
# config/initializers/sqs_active_job.rb
Aws::Rails::SqsActiveJob.configure do |config|
  config.logger = ActiveSupport::Logger.new($stdout)
  config.max_messages = 5
  config.queues = { default: ENV.fetch('SQS_URL', nil) }
  config.client = Aws::SQS::Client.new(region: 'ap-northeast-1')
end

こんな感じで指定していましたが、localstack/localstack:2.0.2のdocker imageを使うようになってからエラーが発生するようになりました。

なので、環境変数で与えてる部分を

SQS_URL=http://localstack:4566/000000000000/ap-northeast-1/app_name

こんな感じで、regionを追加したら動くようになりました。

この方式は

https://docs.localstack.cloud/user-guide/aws/sqs/#queue-urls

に載っている、SQS_ENDPOINT_STRATEGY= path にしたときと同じ動きですが、SQS_ENDPOINT_STRATEGYはどこにも指定して無いです。

githubのlocalstackのソースコードを読めばわかりそうな感じでしたが、そこまでする理由が今回は見当たらなかったので、そこで調査はやめています。

調査は楽しいですが、しすぎるのも良くないと最近思いました。