2019年11月8日金曜日

FireTV アプリでコントローラのボタンをハンドリングしてみた

概要

Web アプリケーションとして作成した FireTV アプリで FireTV のコントローラのイベントハンドリングをしてみました
D-Pad は日本では使えないので Bluetooth コントローラのみ対応しています

環境

  • FireTV 3gen 4K
  • macOS 10.15

ハンドリング用テストアプリ

今回は Web アプリケーション版の FireTV アプリになります
アプリは Ruby + JavaScript で構築します

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim app.rb
require 'sinatra/base'

class TestWebApp < Sinatra::Base
  get '/' do
    erb :index
  end
end
  • vim config.ru
require './app'
run TestWebApp
  • mkdir views
  • vim views/index.erb
<html>
  <head>
    <title></title>
    <style type="text/css">
      body {
        background-color: #FFFFFF;
        margin: 30px;
      }
    </style>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script type="text/javascript">
      $(window).keydown(function(e) {
        $("#keycode").text(e.keyCode);
        return false;
      });
    </script>
  </head>
  <body>
    <h3>down keycode</h3>
    <div id="keycode"></div>
  </body>
</html>

解説

基本は index.erb を見れば OK です
jQuery を使って keycode のハンドリングをしています
keydown を使ってキーボードイベントを取得しそこから e.keyCode で番号が取得できます
あとはその番号を div タグに設定しているだけです

また CSS で背景を白に設定しています
FireTV はデフォルトの背景が黒なのでそのままだと画面が真っ黒になってしまうためです
あと margin も地味に大事でこれがないとテレビによってははみ出してしまうので入れています

動作確認 (ローカル)

  • bundle exec rackup config.ru

http://localhost:9292 にアクセスしてキーボードを叩いてみましょう
叩くごとに keycode が変わるのが確認できると思います

動作確認 (FireTV)

  • bundle exec rackup config.ru -o 0.0.0.0

IP アドレスで Web App Tester からアクセスするのでバインドする IP を 0.0.0.0 にしておきます
Web App Tester を起動しましょう
そして動作しているマシンの IP アドレスを入力してテストアプリとして登録します

あとは起動して動作確認しましょう
静止画ですが実際はコントローラのボタンをポチポチすれば番号が変わるのが確認できると思います

(おまけ) コントローラ keycode 対応表

公式の keycode 表とキーボードの keycode 表を対応付けしてみました
FireTV 上ではなくブラウザ上でキーボードを使ってテストしたい場合に参考にしてください

  • Select (13) -> Enter
  • Up (38) -> 上矢印
  • Down (40) -> 下矢印
  • Left (37) -> 左矢印
  • Right (39) -> 右矢印
  • Play/Pause (179) -> なし
  • Rewind (227) -> なし
  • Fast Forward (228) -> なし
  • Back (NA) -> なし

また公式には書いていなかったですがメニューボタンは「18」でした
音声検索ボタンとホームボタンはハンドリングできなさそうでした

最後に

FireTV のコントローラのボタンのハンドリングをしてみました
Web アプリケーションとして実装している場合は普通に keydown イベントをハンドリングすれば良さそうです

2019年11月7日木曜日

Amazon Fire TV アプリ開発入門 (HTML5+JavaScript編)

概要

FireTV アプリの開発には大きく 2 通りあります

  • Java (Android)
  • HTML5 + JavaScript (Webアプリ)

今回は後者の HTML5 + JavaScript を使った開発方法の入門としてサンプルを実際に FireTV 上で動作させるところまでやってみました

環境

事前準備

今回は Youtube Data API V3 を使うサンプルを動作させます
なので事前に

  • Youtube Data API の有効化
  • Youtube API Key の取得

を行っておきます

Youtube Data API の有効化

GCP コンソールから行います
Youtube Data API を検索して有効にすれば OK です

Youtube API Key の取得

こちらも GCP コンソールの認証情報から「API キー」を作成しましょう
今回は使える API を制限して「Youtube Data API v3」を使えるようにしました
テスト用で面倒ということであれば「キーを制限しない」でも OK です

フレームワークのダウンロード

FireTV アプリを Web アプリケーションとして開発するためのフレームワークがあります
WASK (web-app-starter-kit-for-fire-tv) と呼ばれるフレームワークを使うとコントローラの制御や画面の最適化など勝手にやってくれます
今回はこのフレームワークに含まれるサンプルの動作までを行います

フレームワークは Github で公開されているので clone するだけです

  • git clone https://github.com/amzn/web-app-starter-kit-for-fire-tv.git

API キーを設定する

取得しておいた Youtube API Key を設定します

  • cd web-app-starter-kit-for-fire-tv/out/youtube
  • vim js/init.js
(function(exports) {
    'use strict';
    var settings = {
        Model: YouTubeAPIModel,
        PlayerView: YouTubePlayerView,
        PlaylistView: PlaylistPlayerView,
        showSearch: true,
        skipLength: 30,
        controlsHideTime: 3000,
        user: "amazonwebservices",
        devKey: "AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        showLatestChannel: true,
        channels: [
            {
                type: "playlist",
                id: "PLhr1KZpdzukcQqyY9KShh99W_z-3L48V-",
                title: "AAAAAAAAAAAA"
            }
        ],
        displayButtons: false
    };
    exports.app = new App(settings);
}(window));

書き換えるのは devKey の部分です
ここを取得した API キーにするだけです
他にも firetv.css などがあるので書き換えて背景などを変えてみても良いと思います

  • vim firetv.css
body {
  position: absolute;
  padding: 0;
  margin: 0;
  font-size: 20pt;
  font-family: "Helvetica Neue Light", "HelveticaNeue-Light", Helvetica, Arial, sans-serif;
  /* background: #5a656f url("assets/bg_app.jpg") no-repeat left top; */
  background: #FF9900;
  background-size: 100% 100%;
}

上記の CSS は長いので書き換えた部分ので記載しています

動作確認

とりあえず動作確認したいのであれば index.html をブラウザで開けば確認できます
が、画面サイズなどが FireTV に最適化されたおりブラウザだとうまく表示されないと思います
なので公式のテストツールである Web App Tester を使って FireTV 上でテストしましょう

LAN 内でホスティングする

まずは作成した Web アプリをローカルのマシンでホスティングしましょう
条件としては FireTV が接続しているネットワークと同じネットワーク上でホスティングすれば OK です

ホスティングの方法は何でも OK です
nginx を立ち上げて DocumentRoot に置いても OK ですし serve などを使っても OK です
自分は docker を使ってカレントにあるファイルをホスティングしました

  • docker run -d -p 80:80 -v $(pwd):/usr/share/nginx/html --name wask nginx

ブラウザなどでアクセスできれば OK です

Web App Tester をインストールする

Amazon にあるのでインストールしましょう
少し時間が経てば FireTV 上にアプリが配信されているので確認しましょう

Web App Tester でホスティングしたアプリを FireTV 上で確認する

FireTV を起動してインストールした Web App Tester を立ち上げます
そしてアプリの名前とホスティングしているマシンの IP アドレスを記載します

そして画面の右側にある「デバイスに追加」を選択します

するとアプリとして追加されるので起動しましょう

こんな感じで変更した箇所が反映されたアプリが起動し FireTV 上で確認できます
サンプルなのでちゃんと画面が最適化されコントローラの入力も受け入れるのが確認できると思います

最後に

FireTV アプリケーションを開発するためのフレームワーク「WASK」を使ってサンプルアプリケーションを FireTV 上で動かしてみました
単純に index.html と JavaScript を動かしているだけなのでかなり簡単です

今回はフレームワーク上のサンプルを使いましたが単純に HTML であればいいので自分で 1 から作成しても OK です
ただその場合は画面のスタイルやコントローラのハンドリングなども自分で実装する必要があります

ホスティングも今回はテストなのでローカルで行いましたが本番で動かす場合には Heroku や GCP などを使って動作させる必要があるかなと思います

Android アプリよりかは簡単に作れると思うので個人的にはこちらの方法のほうがおすすめです
Android の場合は Android Studio をインストールしたり adb の操作もあり面倒ですが HTML5+JavaScript であればブラウザさえあれば開発を進められるので簡単です
Android の利点としては既存の Android アプリを FireTV 用のアプリとして公開することもできるという点かなと思います

参考サイト

2019年11月6日水曜日

google-api-javascript-client を使ってみた

概要

google-api-javascript-client を使ってみました
OAuth の実行方法と認証後にセッショントークンを使って Compute の API をコールするところまで試してみました

環境

  • macOS 10.15
  • google-api-javascript-client (2019/11/05 時点)

アプリ雛形作成

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim config.ru
require './app'
run TestWebApp
  • vim app.rb
require 'sinatra/base'

class TestWebApp < Sinatra::Base
  get '/' do
    erb :index
  end
end
  • mkdir views
  • touch views/index.erb

OAuth クライアントの作成

まずはおなじみに OAuth クライアントの作成から行います
作成したらクライアントID とクライアントシークレットをメモしましょう

JavaScript の SDK の場合クレデンシャルファイルを使った認証はできないので文字列の ID とシークレットを使います

OAuth だけ実装してみる

では先に OAuth だけ実装してみましょう
作成した erb ファイルに実装していきます

  • vim views/index.erb
<html>
  <head>
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script src="https://apis.google.com/js/client.js"></script>
    <script type="text/javascript">
      var PROJECT_ID = 'pjct-1234';
      var CLIENT_ID = '123456789012-asdfghjklqwertyuiopzxcvbnm.apps.googleusercontent.com';
      var API_KEY = 'api-key';
      var SCOPES = 'https://www.googleapis.com/auth/compute';
      function authorization() {
        gapi.client.setApiKey(API_KEY);
        gapi.auth.authorize({
          client_id: CLIENT_ID,
          scope: SCOPES,
          immediate: false
        }, function(authResult) {
      console.log(authResult.access_token);
          if (authResult && !authResult.error) {
            $('#ret').text('success');
          } else {
            $('#ret').text('error');
          }
        });
      }
      $(window).load(authorization);
    </script>
  </head>
  <body>
    <p id="ret"></p>
  </body>
</html>

PROJECT_ID は自身の使っているプロジェクトの ID を設定してください
CLIENT_ID, API_KEY は先程メモしておいたものを使ってください

解説

まず SDK を読み込みます

  • <script src="https://apis.google.com/js/client.js"></script>

今回は JQuery も使っています
次に OAuth の処理を実装します
gapi.client.setApiKey で API_KEY をセットします
そして gapi.auth.authorize を使って認証します
今回はスコープに compute の API をコールするためのスコープを追加しています
immediate パラメータは true にすると動作しなかったのでとりあえず false にしています
true にすると認証時にポップアップが表示されないようなのですが動作しなかったので false にしています

あとは認証後のコールバックの関数部分を実装しています
今回は認証の結果だけを表示しています
authResult オブジェクトにセッショントークンの情報が入っています
実際に compute の API をコールするときはこの authResult オブジェクトから勝手に access_token を取得してコールしてくれるので意図的に参照するケースはないかなと思います

動作確認

  • bundle exec rackup config.ru

http://localhost:9292 にアクセスすると認証用のポップアップが表示されます
おそらくポップアップがブロックされるので許可して動作確認しましょう

アカウントを選択します

localhost からのアクセスなので警告がでますがテストなので無視します

compute への権限を許可します

権限を確認して許可しましょう

あとは「success」と表示されれば認証完了です
コンソールにも authResult の情報が表示されているのが確認できると思います

Compute の API をコールする処理を追加

セッショントークンが取得できたら Compute の API をコールしてみましょう

  • vim views/index.erb
<html>
  <head>
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script src="https://apis.google.com/js/client.js"></script>
    <script type="text/javascript">
      var PROJECT_ID = 'pjct-1234';
      var CLIENT_ID = '123456789012-asdfghjklqwertyuiopzxcvbnm.apps.googleusercontent.com';
      var API_KEY = 'api-key';
      var SCOPES = 'https://www.googleapis.com/auth/compute';
      var API_VERSION = 'v1';
      var DEFAULT_PROJECT = PROJECT_ID;
      var DEFAULT_ZONE = 'us-central1-c';
      var DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/compute/v1/rest'];
      function authorization() {
        gapi.client.setApiKey(API_KEY);
        gapi.auth.authorize({
          client_id: CLIENT_ID,
          scope: SCOPES,
          immediate: true
        }, function(authResult) {
          if (authResult && !authResult.error) {
            $('#auth_ret').text('success');
            gapi.client.init({
              discoveryDocs: DISCOVERY_DOCS,
              client_id: CLIENT_ID,
              scope: SCOPES
            }).then(function() {
              var request = gapi.client.compute.instances.list({
                'project': DEFAULT_PROJECT,
                'zone': DEFAULT_ZONE
              });
              request.execute(function(resp) {
                $('#ret').text('success');
                resp.items.forEach(function(i) {
                  $('#instances').append("<li>" + i.name + "</li>");
                });
              });
            })
            .catch(function() {
              $('#ret').text('error');
            });
           } else {
            $('#auth_ret').text('error');
          }
        });
      }
      $(window).load(authorization);
    </script>
  </head>
  <body>
    <h1>Auth</h1>
    <p id="auth_ret"></p>
    <h1>Compute API</h1>
    <p id="ret"></p>
    <ul id="instances"></ul>
  </body>
</html>

デフォルトのゾーンを調べる方法は以下のコマンドを実行しましょう

  • gcloud compute project-info describe --project pjct-1234

デフォルトゾーンが設定されていない場合は自分のインスタンスが存在しているゾーンを設定しましょう

解説

認証後のコールバックメソッドに compute の API をコールする処理を追加しています
今回はすべて同じメソッド内で実装していますが分けても問題ないです

まず gapi.client.init で Compute の API をコールするためのクライアントを初期化します
初期化する際には discoveryDocs という API の定義が配布されている URL があるのでこれを指定します
Compute API の場合は https://www.googleapis.com/discovery/v1/apis/compute/v1/rest になります

次にクライアントの初期化が完了したら API をコールします
gapi.client.compute.instances.list というメソッドを使います
client のあとに「API 名」「リソース名」「操作名」という規則でコールすることができます
引数などは操作名ごとにことなるのでリファレンスを確認してください

最後に結果を受け取ってそれを HTML に表示して終了です
レスポンスの形式は一度 cosole.log などで覗いてみると扱いやすいと思います

ハマリポイント

gapi.client.load は Deprecated になっておりクライアントを初期化する場合は gapi.client.init + discoveryDocs を使いましょう
自分はこの方法にたどり着くまでに「gapi.client.compute is undefined」というエラーにずっと悩まされました
実は gapi.client.load が正常に行われていないために gapi.client.compute が生成されていないのが原因でした

動作確認

  • bundle exec rackup config.ru

http://localhost:9292 にアクセスすると認証用のポップアップが表示されます
すでに認証済みの場合はすぐにポップアップが閉じると思います

しばらくすると以下のようにインスタンスの情報が表示されると思います

最後に

google-api-javascript-client を使って OAuth 処理の実装と Compute API のコールをしてみました
情報があまりなく Compute API をコールするときに少しハマりました

HTML と JavaScript だけで完結するのでサーバが不要な場合に使える方法かなと思います

参考サイト

2019年11月5日火曜日

Google Photos API を使ってみた

概要

Google Photos API を使って Google Photo アプリを作成してみました
OAuth2 認証を使って認証したユーザが持つ写真とアルバムの情報を表示してみます

環境

  • macOS 10.15
  • Ruby 2.6.2p47
  • Google Photos API (2019/11/05 時点)

Google Photos API の有効化

初めての場合はここから API を有効にできます

「Enable the Google Photos Library API」を選択します

プロジェクトを選択するダイアログが表示されるので API を有効にするプロジェクトを選択します

OAuth をコールするクライアントを選択します
今回はブラウザアプリを作成するので「Web browser」を選択します
CORS は全部許可するのでとりあえず入力しないで OK です

API が有効になるのを待ちます

有効になったら API をコールするためのクレデンシャルファイルがダウンロードできます
が、情報が不十分なクレデンシャルなのでダウンロードは一旦保留しておきます

OAuth クライアントの確認/クレデンシャルの取得

API とサービスの画面から「認証情報」を選択します
「OAuth client」というクライアントが作成されているのを確認しましょう

リダイレクト URL が設定されていない場合は設定しましょう
今回は http://localhost:9292/redirect を設定しました
また今回は設定していませんが「承認済みの JavaScript 生成元」に http://localhost:9292 を設定しても OK です
設定したら保存して上にある「JSON をダウンロード」を選択してクレデンシャルの JSON ファイルを保存します
またダウンロードしたクレデンシャルファイルの名前を「client_secrets.json」に変更しましょう

左メニューにある「OAuth同意画面」も設定しましょう

ライブラリインストール

ここからはコーディングです
まずは必要なライブラリをインストールします

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "google-api-client"
  • bundle install --path vendor

サンプルアプリ

ダウンロードした client_secrets.json はアプリのルートディレクトリに配置します

  • vim config.ru
require './app'
run TestWebApp
  • vim app.rb
require 'sinatra/base'
require 'google/api_client/client_secrets'
require 'json'
require 'net/https'

class TestWebApp < Sinatra::Base
  enable :sessions

  before do
    client_secrets = Google::APIClient::ClientSecrets.load
    @auth_client = client_secrets.to_authorization
  end

  get '/' do
    @auth_client.update!(
      :redirect_uri => 'http://localhost:9292/redirect',
      :scope => [
        'https://www.googleapis.com/auth/photoslibrary.readonly',
        'https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata'
      ]
    )
    auth_uri = @auth_client.authorization_uri.to_s
    redirect auth_uri
  end

  get '/redirect' do
    @auth_client.code = request['code']
    @auth_client.fetch_access_token!
    auth_client = Signet::OAuth2::Client.new(JSON.parse(@auth_client.to_json))
    session[:token] = auth_client.access_token
    redirect '/list'
  end

  get '/list' do
    uri = URI.parse 'https://photoslibrary.googleapis.com/v1/albums'
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = Net::HTTP::Get.new uri.request_uri
    req['Content-type'] = 'application/json'
    req['Authorization'] = "Bearer #{session[:token]}"
    res = http.request req
    # "<pre>#{JSON.pretty_generate(JSON.parse(res.body))}</pre>"
    @albums = JSON.parse(res.body)
    erb :list
  end

  get '/:album_id/list' do
    uri = URI.parse 'https://photoslibrary.googleapis.com/v1/mediaItems:search'
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = Net::HTTP::Post.new uri.request_uri
    req['Content-type'] = 'application/json'
    req['Authorization'] = "Bearer #{session[:token]}"
    req.body = { 
      "pageSize" => "100",
      "albumId" => params['album_id']
    }.to_json
    res = http.request req
    @media_items = JSON.parse(res.body)
    erb :items
  end

end
  • mkdir views/
  • vim views/list.erb
<html>
  <head>
    <title>test web app</title>
  </head>
  <body>
    <h1>Albums</h1>
    <ul>
      <% @albums['albums'].each do |album| %>
        <li><a href="/<%= album['id'] %>/list"><%= album['title'] %></a></li>
      <% end %>
    </ul>
  </body>
</html>
  • vim views/items.erb
<html>
  <head>
    <title>test web app</title>
  </head>
  <body>
    <h1>MediaItems</h1>
    <ul>
      <% @media_items['mediaItems'].each do |item| %>
        <li><a href="<%= item['productUrl'] %>" target="_blank"><%= item['filename'] %></a></li>
      <% end %>
    </ul>
  </body>
</html>

説明

まずは OAuth2 認証します
/ にアクセスすると認証が開始されます
認証部分は SDK の機能が使えるのでそのまま使います

認証自体は Google が用意してくれている同意画面を使います
許可するスコープは「https://www.googleapis.com/auth/photoslibrary.readonly」と「https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata」になります
前者はアルバムへのアクセス権限で後者はアルバムの配下の写真にアクセスするための権限です
Google 側で認証と認可が成功するとアプリの /redirect に戻ってきます
認証が成功していると Google Photos API をコールするためのセッショントークンが返ってきます

このセッショントークンを使って Google Photos API をコールします
Ruby の google-api-client には Google Photos API の SDK が含まれていないので直接 REST API をコールしています
今回はアルバムの一覧を表示する画面とアルバム配下の写真の一覧を表示するビューを作成しています
写真は 100 枚ごとに取得しておりもし次のページが存在する場合は nextPageToken が付与されています
これを使ってリクエストすることで次のページの写真を取得することができます (参考)

動作確認

  • bundle exec rackup config.ru

http://localhost:9292 にアクセスしましょう
認証画面が出るのでユーザを選択します

警告画面が表示されますがテストなので気にせず移動します
もし「承認済みの JavaScript 生成元」を設定している場合はこの警告はでません

アプリが使用する権限が表示されるので「許可」します

内容を確認して許可します

最初にアルバムの一覧が表示されます

アルバムを選択するとアルバム配下の写真の一覧が表示されます
写真のリンクをクリックすると実際の写真を確認することができます

最後に

Google Photos API を Ruby からコールしてみました
Ruby には専用の SDK がなかったですが Java と Node.js には実装されているようです
API をコールする前に OAuth2 の認証を設定/実装する必要があるのでそっちが大変かなと思います

また今回はアルバムと写真の取得の API のみ使っています
他にも共有アルバムを取得する API や写真をアップロードする API もあるので興味があれば試してみてください

Tips

{"error": "unsupported_grant_type", "error_description": "Invalid grant_type: "} が出る場合はクレデンシャルの JSON ファイルの情報が不足している可能性があるので再度ダウンロードし直してください

参考サイト

2019年11月2日土曜日

The resource 'projects/product-123456/global/firewalls/default-allow-ssh' is not ready

概要

GCP のファイアウォールを操作するときにタイトルのエラーが発生し続けファイアウォールの操作ができなくなりました
対処方法を紹介します

対処方法

対処方法ではないのかもしれませんがいろいろ試した結果解消しなかったので同じルールを再作成しました

  • gcloud compute firewall-rules delete default-allow-ssh
  • gcloud compute firewall-rules create default-allow-ssh --network default --allow tcp:22 --priority 65534

たぶんこれが一番てっとり早いはず

2019年11月1日金曜日

iPhoneXR で撮影した動画のファイルサイズ

概要

iPhoneXR で 3 時間ほど連続で動作撮影をした場合のファイルサイズを調べてみました

環境

  • iPhoneXR 64GB
  • iOS 13.2

撮影時間

  • 03:03:02

ファイルサイズ

  • 13.61GB

考察

1 時間あたりの容量は 13.61GB / 3h = 4.5GB くらいになると思います

iPhone or iOS が動画のサイズに応じて圧縮効率を上げるような処理を入れているのであれば時間が増えてもサイズは下げるかもしれません
1, 2 時間のときの動画サイズも測定すれば更に正確な予測値が出ると思います

動画にはしていましたがほぼ動いていない状態+音もないので状況によっては容量が上下するかもしれません

2019年10月27日日曜日

マイクラで 3D 酔いしないための努力

概要

FPS 視点のゲームはよく 3D 酔いすると言われています
自分も 3D 酔いになりやすく長時間続けるのが大変です
自分なりに少しでも軽減させる方法があったので紹介します

環境

  • MacBookAir 14inch
    • CPU Dual-Core Intel Core i7
    • メモリ 8GB
    • GPU Intel HD Graphics 5000 (オンボード)
  • macOS 10.15
  • Minecraft JavaEdition 1.14

方法

ラップトップではやらない

なぜか自分はラップトップ上でやるとすぐに酔います
Mac Book Air の 14inch で起動しラップトップのキーボードと無線のマウスを使ってプレイしています
おそらく画面が近いのが酔いの原因ではないのかと思っています

近くで見ない、大画面で離れてプレイ

プレイする場合はラップトップでプレイせずにクラムシェルにして thunderbult で外部ディスプレイに接続してプレイ画面を移すようにしています
そしてキーボードは MagicKeyboard2 にしています
ディスプレイのサイズは 32inch でそのディスプレイから 3 - 4m くらい離れてプレイしています
この状況にしてからはラップトップでプレイしている時よりか症状が改善しました

頭を固定する

またプレイするときはなるべく頭を動かさないようにしています
理由はわかりませんが頭が動く状態だと酔いやすかったです
自分の場合はハイバックっぽい背もたれ付きの椅子を使っているので頭を背もたれに接着させることでなるべく動かないように固定しています

画面がカクつく止まる場合は酔いやすい

マシンスペックが低いことで描画が遅れ本来思っていた描画と異なることで酔いやすくなるようです
酔いやすい場合は 60fps 出ているか確認しましょう

改善しない場合ははマイクラの設定を見直しましょう
それでもダメな場合は思い切ってゲーミング PC レベルのハイスペックなマシン (特に CPU と GPU を積んだマシン) に買い換えるのもありかなと思います

ちなみに自分の環境のスペックでも 60fps は常に出ません
Mob が多かったりパーティクルが激しい場所では 20 - 30 fps 程度に落ちます
そういう状況の場所ではなるべくプレイしないようにしています

Optifine などの Mod を入れてシェーダーなどの設定を変更してマシンの負荷を下げるのも良いと思います

設定関連

マイクラの画面設定などを見直しましょう
個人的に fps に特に影響するのが「最大描画チャンク」かなと思います
これが少なかければ少ないほど fps が上がりました
しかし最大描画チャンク数が小さすぎるほどプレイに影響するので最低 8 or 9 にはしましょう
具体的には値が小さすぎると近くにある食物などが育たなくなったりします
またその状態で 60fps 以上出ないと酔いやすくなるかなと思います

あとは

  • ビデオ設定 -> グラフィックス -> 処理優先
  • ビデオ設定 -> アニメーションの設定 -> すべてオフ
  • ビデオ設定 -> 画面の揺れ -> オフ

などなどビデオ設定を負荷がかからないように調整するのがポイントです

臨場感のある視野角をオンにする

個人的にはこれも効果があると思います
ゲームへの没入感を高まるので酔いにくくなるかなと思います

慣れ

人によっては全然酔わないという人もいるようです
そういった人は実はハイスペックマシンで常に 60fps 以上出ているのかもしれませんが、、
fps が出ていない状態でカクカクの状況でも酔わない人は酔わないのかもしれません

あとはどうしてもプレイを続けたいのであれば酔い止めなどの薬に頼るのも手だと思いますが、そこまでして頑張る必要はないと思います